How to Update Redux Selectors in React

This article provides a direct guide on how to update and refactor Redux selectors in React applications. You will learn how to transition from basic selector functions to optimized, memoized selectors using Redux Toolkit, how to update selectors to handle dynamic arguments, and how to ensure your React components re-render efficiently when the Redux state updates.

Understanding the Need to Update Selectors

Selectors are functions that extract specific pieces of data from the Redux store. As your application grows, basic inline selectors can cause unnecessary component re-renders because they return new object references every time they run. Updating your selectors to use memoization ensures that components only re-render when the underlying data actually changes.

Step 1: Upgrading Basic Selectors to Memoized Selectors

Basic selectors simply retrieve state. When updating these to handle complex data transformations (like filtering or mapping), you should update them using createSelector from Redux Toolkit (which integrates Reselect).

Before: A Basic, Non-Memoized Selector

// This runs on every state change and returns a new array reference
const selectCompletedTodos = (state) => {
  return state.todos.filter(todo => todo.completed);
};

After: Updated Memoized Selector

To update this selector for optimal performance, wrap it with createSelector. It will now only recalculate if state.todos changes.

import { createSelector } from '@reduxjs/toolkit';

const selectTodos = (state) => state.todos;

export const selectCompletedTodos = createSelector(
  [selectTodos],
  (todos) => todos.filter(todo => todo.completed)
);

Step 2: Updating Selectors to Accept Dynamic Arguments

Sometimes you need to update a selector so it can accept arguments from a component, such as an ID or a search query.

To do this, update your selector definition to accept input parameters, and pass those parameters through the useSelector hook in your React component.

Defining the Parameterized Selector

import { createSelector } from '@reduxjs/toolkit';

const selectTodos = (state) => state.todos;
const selectTodoId = (state, todoId) => todoId;

export const selectTodoById = createSelector(
  [selectTodos, selectTodoId],
  (todos, todoId) => todos.find(todo => todo.id === todoId)
);

Using the Updated Selector in a Component

Pass the dynamic argument inside the useSelector hook.

import { useSelector } from 'react-redux';
import { selectTodoById } from './todoSelectors';

const TodoItem = ({ id }) => {
  // Pass the state and the dynamic id to the selector
  const todo = useSelector((state) => selectTodoById(state, id));

  return <div>{todo?.text}</div>;
};

Step 3: Handling State Updates with useSelector

When the Redux state is updated via dispatched actions, React Redux automatically runs your selectors.

If you are updating a selector that returns a new object or array, you must ensure you use a shallow equality check to prevent infinite loops or unnecessary renders. Update your component’s useSelector hook by passing shallowEqual as the second argument:

import { useSelector, shallowEqual } from 'react-redux';
import { selectUserMetadata } from './userSelectors';

const UserProfile = () => {
  // Use shallowEqual when the updated selector returns a new object
  const userMetadata = useSelector(selectUserMetadata, shallowEqual);

  return <div>{userMetadata.name}</div>;
};