A problem with useState()
design
useState() returning an array
const [ text, setText ] = useState('');
- You should remember the order of the elements in the returned array.
useState()
returning an object
Let's say you destructure the returned object of useState()
into variables.
const { state, setState } = useState();
- You don't need to remember the order of the properties in the object.
What if you need to rename the properties like this? You probably want to name them as you want because it's easier to understand code when the variable names are set based on the context, how they are used.
const { state: text, setState: setText } = useState('');
Then you run into another problem.
- You always need to specify the 'replaced' property names(
state
,setState
) and 'replacing' property names(text
,setText
).
The problem looks more clear when you have multiple states.
const { state: text, setState: setText } = useState('');
const { state: age, setState: setAge } = useState(0);
const { state: isLoading, setState: setIsLoading } = useState(true);
While you could have done this.
const [ text, setText ] = useState('');
const [ age, setAge ] = useState(0);
const [ isLoading, setIsLoading ] = useState(true);
Conclusion
Therefore, we can conclude.
useState() traded off the inconvenience of remembering element order to avoid repetitive boilerplate code.”
Playing with custom hooks
We can follow the useState()
philosophy when we design our own hooks.
export default function useDebounce<T>(value: T): T {
const [debouncedValue, setDebouncedValue] = useState(value);
const debounce = (value: T) => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, DELAY);
return timer;
};
const reset = (timer: number) => {
clearTimeout(timer);
};
useEffect(() => {
const timer = debounce(value);
return () => {
reset(timer);
};
}, [value]);
return debouncedValue;
}
-
useDebounce
is used to delay the operation until the value doesn't change for some time. - Since there is just one value returned from the hook, it's straightforward to return the wanted value.
export default function useFormSubmission(
submitCallback: () => Promise<unknown>,
) {
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const handleSubmit = async (
event?: FormEvent<HTMLFormElement> | MouseEvent<HTMLButtonElement>,
) => {
if (event) {
event.preventDefault();
}
if (isSubmitting) return;
setIsSubmitting(true);
await submitCallback();
setIsSubmitting(false);
};
return { isSubmitting, handleSubmit };
}
-
useFormSubmission
is used to prevent the client from submitting the form again when it's being sent. - In terms of custom hook, I find it better to return an object instead of an array for the following reasons.
- Usually, there is no common agreement or standard when you create custom hooks. So it's hard to memorize the order of properties.
Top comments (1)
Good analysis. I would like to add that the data structure that is returned by useState, would be refered to as a tuple in other languages. It's a common data structure for a list of data values that belong together in a certain order. It's not the same as an array. The order of and values in an array are not static.