DEV Community

Chan
Chan

Posted on

[ state, setState ] instead of { state, setState }

A problem with useState() design

useState() returning an array

const [ text, setText ] = useState('');
Enter fullscreen mode Exit fullscreen mode
  • 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();
Enter fullscreen mode Exit fullscreen mode
  • 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('');
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

While you could have done this.

const [ text, setText ] = useState('');
const [ age, setAge ] = useState(0);
const [ isLoading, setIsLoading ] = useState(true);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
  • 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 };
}
Enter fullscreen mode Exit fullscreen mode
  • 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)

Collapse
 
brense profile image
Rense Bakker

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.