How to Implement Redux Selectors in React

This article provides a practical guide on how to implement Redux selectors in a React application to query and derive state efficiently. You will learn how to create basic selectors, integrate them with the useSelector hook, and use the Redux Toolkit’s createSelector utility to build memoized selectors that optimize your application’s performance.

What is a Redux Selector?

A selector is a pure function that takes the Redux state as an argument and returns a specific slice of that state, or derives new data from it. Selectors encapsulate the structure of your state, meaning that if you change the shape of your Redux store in the future, you only need to update the selector rather than every component that queries that data.

Step 1: Writing Basic Selectors

Basic selectors simply retrieve raw data directly from the Redux store. They are typically defined alongside your Redux slices.

// features/cart/cartSlice.js

// Example state structure: { cart: { items: [...], discount: 0.1 } }

export const selectCartItems = (state) => state.cart.items;
export const selectDiscount = (state) => state.cart.discount;

Step 2: Using Selectors in React Components

To use these selectors inside your React components, import the useSelector hook from react-redux and pass the selector function to it.

// components/Cart.jsx
import React from 'react';
import { useSelector } from 'react-redux';
import { selectCartItems } from '../features/cart/cartSlice';

export const Cart = () => {
  const items = useSelector(selectCartItems);

  return (
    <div>
      <h2>Your Cart</h2>
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.name} - ${item.price}</li>
        ))}
      </ul>
    </div>
  );
};

Step 3: Implementing Memoized Selectors with Reselect

When you need to compute derived data (such as filtering a list or calculating a total price), a basic selector will recalculate on every render. To prevent unnecessary recalculations and re-renders, you should use memoized selectors.

Redux Toolkit includes the createSelector function from the Reselect library for this purpose.

// features/cart/cartSlice.js
import { createSelector } from '@reduxjs/toolkit';

// Input selectors (basic selectors)
export const selectCartItems = (state) => state.cart.items;
export const selectDiscount = (state) => state.cart.discount;

// Memoized selector
export const selectCartSubtotal = createSelector(
  [selectCartItems],
  (items) => items.reduce((total, item) => total + item.price, 0)
);

// Memoized selector that depends on other selectors
export const selectCartTotalWithDiscount = createSelector(
  [selectCartSubtotal, selectDiscount],
  (subtotal, discount) => subtotal * (1 - discount)
);

How Memoization Works

The createSelector function accepts an array of input selectors and an output combiner function.

  1. It runs the input selectors and grabs their results.
  2. If the results of the input selectors have not changed since the last run (using strict === equality reference checks), it skips running the output function and returns the cached result.
  3. If any input value has changed, it executes the output function with the new values, caches the new result, and returns it.

Step 4: Using Memoized Selectors in Components

You consume memoized selectors in your components using the exact same syntax as basic selectors.

// components/OrderSummary.jsx
import React from 'react';
import { useSelector } from 'react-redux';
import { selectCartTotalWithDiscount } from '../features/cart/cartSlice';

export const OrderSummary = () => {
  const total = useSelector(selectCartTotalWithDiscount);

  return (
    <div className="summary">
      <h3>Total Amount Due: ${total.toFixed(2)}</h3>
    </div>
  );
};