DEV Community

Sumit Wadhwa
Sumit Wadhwa

Posted on

Why doesn't useState has a dependency array?

This is how I'm updating a custom hook's state based on the prop passed to it:

const useCustomHook = (prop: number) => {
   const [state, setState] = useState(prop);

   // this is how I'm currently updating state
   // when the props change.
   useEffect(() => {
      setState(prop);
   }, [prop]);

   return state;
};

// it's usage
const [localState, setLocalState] = useState(0);
const hookState = useCustomHook(localState);
Enter fullscreen mode Exit fullscreen mode

But the problem with above approach is that the return state of the custom hook is going to run first, returning the old value before running the useEffect callback and updating the hook's state and then returning the new state. This will cause the main component to render twice.

Now you may ask, what's the big deal if it renders twice? It can lead to bugs. For example, the custom hook could be a count-down, implementing setInterval, that comes down to 0, but when I reset it with setLocalState(5), the useCustomHook will immediately return 0 and not 5.

Why doesn't useState return updated value based on if the value passed to it is changed? or why doesn't it have a dependency array like useEffect if we don't want to change its behaviour.

Thanks for your time.

Top comments (8)

Collapse
 
jcubic profile image
Jakub T. Jankiewicz

I think it's kind of odd example if you call useState in useEffect the component will be caled twice which is normal behavior. Components are not guarantee to be called only once. They can be called multiple times. The most performance issue came from double update the DOM, which will not happen in this case.

Can you show real use case why you would call setState in useEffect?

Collapse
 
swadhwa16 profile image
Sumit Wadhwa • Edited

How would you propose changing state of a hook based on the prop passed to it?
An example would a count-down timer with reset functionality. If we're passing some future time to this custom hook, then the hook needs to update its internal state as well.

Collapse
 
jcubic profile image
Jakub T. Jankiewicz • Edited

You define state inside the hook and return setValue and value from the hook, just like useState does you don't pass state to hook.

const useTimer = () => {
   const [count, setCount] = useState(0);
   function increment() {
     setCount(count+1);
   }
   function decrement() {
     setCount(count-1);
   }
   return {count, increment, decrement};
};
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
swadhwa16 profile image
Sumit Wadhwa

yes I think you're right. a hook's state should not depend on its props. it should be private and it should expose functions that trigger hook's state change so that we have more control over hook's state. thanks.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

In hooks props are often used for state initialization:

const useCounter = (initialCount = 0) => {
   const [count, setCount] = useState(initialCount);
   function increment() {
     setCount(count+1);
   }
   function decrement() {
     setCount(count-1);
   }
   return {count, increment, decrement};
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tylim88 profile image
Acid Coder • Edited

because it happens in different render

when you setLocalState, you update your state to 0 and pass it to the hook, that is one render

useEffect only run at the next render and if there is a change in dependencies, so the first render has to run first (with old value), then it trigger useEffect that set another state and trigger another render, only by that you have the correct value

but i have to say this is a very odd way to update the state, there is pattern that is much better

Collapse
 
pengeszikra profile image
Peter Vivo

Thats wierd because you store state in your custom hook, wich isn't good idea. Instead just use outer state, and your problem is solved.

Collapse
 
swadhwa16 profile image
Sumit Wadhwa

Yes you're right. thank you.