Last active
October 23, 2020 13:02
-
-
Save cameron-martin/2a877ae66f5847d4676b2c166aa4b49c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useMemo } from "react"; | |
interface DataBinding<T> { | |
value: T; | |
setValue(value: T | ((prev: T) => T)): void; | |
} | |
/** | |
* Memoises the return value of a function based on the first argument by using a weakmap for the cache. | |
* @param f | |
*/ | |
function weakCacheOne<U extends object, V>( f: (arg: U) => V): (arg: U) => V { | |
const cache = new WeakMap<U, V>(); | |
return arg => { | |
if(cache.has(arg)) { | |
return cache.get(arg)!; | |
} | |
const returnValue = f(arg); | |
cache.set(arg, returnValue); | |
return returnValue; | |
}; | |
}; | |
const lowerProps = weakCacheOne(function <T>(binding: DataBinding<T>): { [K in keyof T]: DataBinding<T[K]> } { | |
const bindings = new Map(); | |
return new Proxy(binding, { | |
get(target, prop) { | |
if(!bindings.has(prop)) { | |
bindings.set(prop, { | |
value: target.value[prop], | |
setValue(valueOrMutation) { | |
binding.setValue(oldValue => ({ | |
...oldValue, | |
[prop]: typeof valueOrMutation === 'function' ? valueOrMutation(oldValue[prop]) : valueOrMutation | |
})); | |
} | |
}); | |
} | |
return bindings.get(prop); | |
}, | |
}) as any; | |
}); | |
// ... more of these for lenses, arrays, etc. | |
interface UseFormResult<T> { | |
onSubmit(event: React.FormEvent<HTMLElement>): void; | |
isSubmitting: boolean; | |
binding: DataBinding<T> | |
} | |
function useForm<T>(initialValue: T, onSubmit: (value: T) => void | Promise<void>): UseFormResult<T> { | |
const [value, setValue] = useState(initialValue); | |
const [isSubmitting, setIsSubmitting] = useState(false); | |
return useMemo(() => ({ | |
isSubmitting, | |
binding: { | |
value, | |
setValue, | |
}, | |
onSubmit() { | |
// TODO | |
}, | |
}), [value, setValue, isSubmitting]); | |
} | |
function MyComponent() { | |
const { binding, onSubmit, isSubmitting } = useForm({ name: "Example name", marketingConsent: true }, () => undefined); | |
const { name, marketingConsent } = lowerProps(binding); | |
return ( | |
<form onSubmit={onSubmit}> | |
<TextInput bind={ name } /> | |
<Checkbox bind={ marketingConsent } /> | |
<input type="submit" disabled={isSubmitting} value="Submit" /> | |
</form> | |
); | |
} | |
interface TextInputProps { | |
bind: DataBinding<string>; | |
type?: "text" | "password"; | |
} | |
function TextInput({ bind, type = "text" }: TextInputProps) { | |
return <input type={ type } value={ bind.value } onChange={ event => bind.setValue(event.target.value) } /> | |
} | |
interface CheckboxProps { | |
bind: DataBinding<boolean>; | |
} | |
function Checkbox({ bind }: CheckboxProps) { | |
return ( | |
<input | |
type="checkbox" | |
checked={bind.value} | |
onChange={ event => bind.setValue(event.target.checked) } | |
/> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment