Skip to content

Instantly share code, notes, and snippets.

@shikelong
Created September 16, 2021 05:36
Show Gist options
  • Save shikelong/5e10eeb553ee83007e73bae5a2a56b17 to your computer and use it in GitHub Desktop.
Save shikelong/5e10eeb553ee83007e73bae5a2a56b17 to your computer and use it in GitHub Desktop.
Hook help write Controlled Component Pattern
import * as React from 'react';
export default function useControlledState<T, R = T>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
onChange?: (value: T, prevValue: T) => void;
postState?: (value: T) => T;
}
): [R, (value: T) => void] {
const { defaultValue, value, onChange, postState } = option || {};
const [innerValue, setInnerValue] = React.useState<T>(() => {
if (value !== undefined) {
return value;
}
if (defaultValue !== undefined) {
return typeof defaultValue === 'function' ? (defaultValue as any)() : defaultValue;
}
return typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
});
let mergedValue = value !== undefined ? value : innerValue;
if (postState) {
mergedValue = postState(mergedValue);
}
function triggerChange(newValue: T) {
setInnerValue(newValue);
if (mergedValue !== newValue && onChange) {
onChange(newValue, mergedValue);
}
}
// Effect of reset value to `undefined`
const firstRenderRef = React.useRef(true);
React.useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false;
return;
}
if (value === undefined) {
setInnerValue(value as any);
}
}, [value]);
return [mergedValue as unknown as R, triggerChange];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment