useSelector selector function called unexpectedly on re-renders

Viewed 353

I have a simple app (view on stackblitz) making use of useSelector

const TodoList = () => {

  const [count, setCount ] = useState(0);

  const todos = useSelector(state => {
    console.log('useSelector called')
    return state.todos
  });

  useEffect(() => {
  const timer = setInterval(() => {
    setCount(count++)
  }, 1000);
  return () => clearTimeout(timer);
}, []); 
  return <p>{count}</p>
}
export default TodoList

There is a timer updating a count variable and displaying it in a view. What I do not understand is the selector function of useSelector is called each time the FC is re-rendered.

According to the docs (or my interpretation) this shouldn't occur

The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector).

Questions are

1) how to prevent the re-running of the selector?

2) If my interpretation of the docs is wrong what situation is the docs referring to?

1 Answers

I think that part of the docs may be a little bit confusing. If you read a little further down in the docs, it’s a little clearer https://react-redux.js.org/api/hooks#equality-comparisons-and-updates

It sounds like the callback function has to be unchanged in order for it not to be called again on subsequent render. As-is you are creating a new reference on every render.

Since your selector doesn't have any closure dependencies, you can define it outside of your component. Something like:

const todosSelector = state => state.todos

const TodoList = ({ todos, toggleTodo }) => {
  ...
  const todos = useSelector(todosSelector)
  ...
}

You also have the option of employing useMemo or useCallback if for some reason you need to define it in your component.

Related