React: Hooks bug it re-renders when calling setX for the same value

Created on 9 Feb 2019  路  5Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
out

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:

See https://codesandbox.io/s/x31lqr09zq

What is the expected behavior?
If I click "count = count", it should not execute the function again

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
16.8.1. All browsers have the same issue

Most helpful comment

This may be unintuitive, but it's the expected behavior. See the docs: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update. When you pass the same state value, React will call the component render function but it won't recurse to the children or run any effects.

If you're curious, this is because the updates are not processed until the render phase. For example if you write setCount(c => c) (a no-op state update), that state updater function is called during the render (we do this for consistency with useReducer, which does this in order to have access to the latest props). So there's no way to skip it. Similarly, if you were to call setCount(c => c + 1); setCount(count); with two updates that "cancel out", that isn't known until the render function is called.

All 5 comments

This may be unintuitive, but it's the expected behavior. See the docs: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update. When you pass the same state value, React will call the component render function but it won't recurse to the children or run any effects.

If you're curious, this is because the updates are not processed until the render phase. For example if you write setCount(c => c) (a no-op state update), that state updater function is called during the render (we do this for consistency with useReducer, which does this in order to have access to the latest props). So there's no way to skip it. Similarly, if you were to call setCount(c => c + 1); setCount(count); with two updates that "cancel out", that isn't known until the render function is called.

Hey @sophiebits, Thanks for the explanation.

My following question is not about this issue. It is about the comparison algorithm for bailing out of a state update. Why not using the shallowEqual??

2

See https://codesandbox.io/s/13zn77kq2l

shallowEqual would be slower and not always what you want. However if you want shallowEqual behavior you can write it yourself using something like this:

function useStateWithShallowEqual(initialValue) {
  let [value, setValue] = useState(initialValue);
  let setWithShallowEqual = useCallback(function (newValue) {
    setValue(val => {
      if (typeof newValue === 'function') {
        newValue = newValue(val);
      }
      if (shallowEqual(val, newValue)) {
        return val;
      } else {
        return newValue;
      }
    });
  }, []);
  return [value, setWithShallowEqual];
}

https://codesandbox.io/s/jv7lxyq253

Your answer is very important to me. Thanks!!

Hi @sophiebits, according to your answer, I've figured out that it only causes an extra re-render when value is changed then be updated with the same again.
Look at my sample code at https://codesandbox.io/s/aged-architecture-jhm6o
If you click the 2nd for the first time, no re-render trigger.
But if you click the 1st button, then click the 2nd button again, it causes an extra re-render.
And by the way, can you explain more why it need to re-render 1 time when it updates with the same value. Thanks in advance

Was this page helpful?
0 / 5 - 0 ratings