question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

useSelector with equality check still triggers useEffect

See original GitHub issue

Do you want to request a feature or report a bug? I have a feeling this is by design, and if so we should look to update the documentation for https://react-redux.js.org/next/api/hooks#equality-comparisons-and-updates

as it reads like creating a memoized selector or using an equalityFn argument in useSelector have the same result in the component rendering process.

What is the current behavior? I have a simplified example of the example. https://codesandbox.io/s/great-tree-7r9if. setToggle is used as a forceUpdate of the component independent of the two changing values in the store, testString and testObject.

The component is subscribed to testString and testObject as a memoized selector and a useSelector with equality check. Below are the examples in question.

on Button press with no force update (removing setToggle).

  • testString useEffect not fired (as expected)
  • testSelector useEffect not fired (as expected memoized selector with shallowEqual check)
  • testObject useEffect not fired (as expected equalityFn check returns true, no new testObject value is returned).

on Button press with forced component update.

  • testString useEffect not fired (as expected)
  • testSelector useEffect not fired (as expected memoized selector with shallowEqual check)
  • testObject useEffect is fired (as expected?? shallowEqual equalityFn means that this is not the cause of the re-render, but seems to return a different object reference and therefore will trigger the useEffect)

What is the expected behavior? Based on the documentation it seems that creating a memoized selector and using the equalityFn check will result in the same behaviour, so if the equalityFn is truthy then it returns the same store reference (not trigger re-render or useEffect call). I have a feeling this is not really possible with this api, so may we need to update the docs to clarify this and maybe recommend the memoized selector approach or many useSelector instances which return primitive values.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

12reactions
reduxdjcommented, Jan 30, 2020

I stumbled across this when i was trying to understand why my selector wasn’t working, the problem I didn’t notice is that useSelector has as strict comparison, so it’s good for strings but not really useful with the way we program in yee 'ol immutable state. The second argument for useSelector takes a comparator function, and I usually just stick in isEqual from lodash.

useSelector(state => state.someCoolMusicList, isEqual);

Hope this helps, break it down a little easier.

2reactions
markeriksoncommented, Aug 16, 2019

I’m not quite clear exactly what point you’re trying to make here. Can you clarify?

Also, I note that the createSelector call in the sandbox is actually useless. Any time you have a value => value output selector in createSelector, you’re using it wrong.

A better example of a memoized selector would be filtering a list of visible todos from the Redux todos demo. (I just showed how to convert that filtering to Reselect in a tutorial for Redux Starter Kit.)

That selector looks like:

const selectVisibleTodos = createSelector(
  [selectTodos, selectFilter],
  (todos, filter) => {
    switch (filter) {
      case VisibilityFilters.SHOW_ALL:
        return todos
      case VisibilityFilters.SHOW_COMPLETED:
        return todos.filter(t => t.completed)
      case VisibilityFilters.SHOW_ACTIVE:
        return todos.filter(t => !t.completed)
      default:
        throw new Error('Unknown filter: ' + filter)
    }
  }
)

Let’s say that the filter has been set to SHOW_ALL. If this selector weren’t memoized at all, it would be returning a new array every time any action is dispatched, because it’s always doing the filter work. However, with memoization, the todos.filter() call only happens if either the todos array or the filter value have changed from the last time.

That means that if an action like "INCREMENT" is dispatched, this memoized selector will return the same array reference as last time. useSelector()'s default === comparison will see that the result array is the same reference, and not force a re-render.

For point of comparison, here’s what would happen if we did not memoize the selector, but did use shallowEqual as an equalityFn. The non-memoized selector would return a new filtered array reference, but shallowEqual would see that the old and new filtered arrays are equivalent (same length field, identical content references). So, the comparison would return true, and again useSelector() would not force a re-render.

However, all that only applies when a Redux action is dispatched. If the component starts to render for any other reason (parent re-rendering, a useState() setter being called, etc), the selector just returns whatever value it has. If we again used the non-memoized filtered todos selector, it would return a new array reference, and that would trigger a useEffect() that has the returned array in its dependency list.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Redux Gotchas — useSelector - Level Up Coding
Unfortunately, even with deep equality check, it is still unable to prevent accidental triggering of useEffect() .
Read more >
Hooks - React Redux
useSelector () uses strict === reference equality checks by default, not shallow equality (see the following section for more details).
Read more >
How useSelector can trigger an update only when we want it to
I am a big fan of React's context api, but performance issues can crop up when two pieces of state live in a...
Read more >
useSelector with useDispatch causing infinite loop
This is happening because due to the fact that you're using a part of the Redux state (i.e. the posts which you extract...
Read more >
React-redux useSelector hook and equality checks
useSelector hook will cause your component to be rerender every time an action is dispatched. The solution for this problem is to introduce ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found