Skip to content

Instantly share code, notes, and snippets.

@cameron-martin
Last active October 23, 2020 13:02
Show Gist options
  • Save cameron-martin/2a877ae66f5847d4676b2c166aa4b49c to your computer and use it in GitHub Desktop.
Save cameron-martin/2a877ae66f5847d4676b2c166aa4b49c to your computer and use it in GitHub Desktop.
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