How to correctly use a curried selector function with react-redux's useSelector hook?

When the return value of a selector is a new function, the component will always re-render on each store change.

useSelector() uses strict === reference equality checks by default, not shallow equality

You can verify this with a super simple selector:

const curriedSelector = state => () => 0;

let renders = 0;
const Component = () => {
  // Returns a new function each time
  // triggers a new render each time
  const value = useSelector(curriedSelector)();
  return `Value ${value} (render: ${++renders})`;
}

Even if the value is always 0, the component will re-render on each store action since useSelector is unaware that we're calling the function to get the real value.

But if we make sure that useSelector receives the final value instead of the function, then the component only gets rendered on real value change.

const curriedSelector = state => () => 0;

let renders = 0;
const Component = () => {
  // Returns a computed value
  // triggers a new render only if the value changed
  const value = useSelector(state => curriedSelector(state)());
  return `Value ${value} (render: ${++renders})`;
}

Conclusion is that it works, but it's super inefficient to return a new function (or any new non-primitives) from a selector used with useSelector each time it is called.

props can be used through closure (see the examples below) or by using a curried selector.

The documentation meant either:

  • closure useSelector(state => state.todos[props.id])
  • curried useSelector(state => curriedSelector(state)(props.id))

connect is always available, and if you changed your selector a little, it could work with both.

export const getTodoById = (state, { id }) => /* */

const Component =  props => {
  const todo = useSelector(state => getTodoById(state, props));
}
// or
connect(getTodoById)(Component)

Note that since you're returning an Object from your selector, you might want to change the default equality check of useSelector to a shallow equality check.

import { shallowEqual } from 'react-redux'

export function useShallowEqualSelector(selector) {
  return useSelector(selector, shallowEqual)
}

or just

const todo = useSelector(state => getTodoById(state, id), shallowEqual);

If you're performing costly computations in the selector or the data is deeply nested and performance becomes a problem, take a look at Olivier's answer which uses memoization.


Here is a solution, it uses memoïzation to not re-render the component on each store change :

First I create a function to make selectors, because the selector depends on the component property id, so I want to have a new selector per component instances.

The selector will prevent the component to re-render when the todo or the id prop hasn't changed.

Lastly I use useMemo because I don't want to have more than one selector per component instance.

You can see the last example of the documentation to have more information

// selectors
const makeGetTodoByIdSelector = () => createSelector(
   state => state.todo.byId,
   (_, id) => id,
   (todoById, id) => ({
       ...todoById[id], 
       display: getFancyDisplayName(todoById[id])
   })
);

const getFancyDisplayName = t => `${t.id}: ${t.title}`;

// example component
const TodoComponent = () => {
  // get id from react-router in URL
  const id = match.params.id && decodeURIComponent(match.params.id);

  const getTodoByIdSelector = useMemo(makeGetTodoByIdSelector, []);

  const todo = useSelector(state => getTodoByIdSelector(state, id));

  return <span>todo.display</span>;
}

Yes, it is how it's done, simplified example:

// Curried functions
const getStateById = state => id => state.todo.byId[id];

const getIdByState = id => state => state.todo.byId[id];

const SOME_ID = 42;

const TodoComponent = () => {
  // id from API
  const id = SOME_ID;

  // Curried
  const todoCurried = useSelector(getStateById)(id);
  const todoCurried2 = useSelector(getIdByState(id));

  // Closure
  const todoClosure = useSelector(state => state.todo.byId[id]);

  // Curried + Closure
  const todoNormal = useSelector(state => getStateById(state)(id));

  return (
    <>
      <span>{todoCurried.display}</span>
      <span>{todoCurried2.display}</span>
      <span>{todoClosure.display}</span>
      <span>{todoNormal.display}</span>
    </>
  );
};

Full example:

Edit styled-antd-react-starter