Reselect is a simple yet powerful library for creating memoized, composable selector functions for your Redux store. While primarily associated with Redux, its core concept of memoization can be applied in other state management patterns as well.
What is a Selector?
In the context of Redux, a selector is a pure function that takes the Redux state as an argument and returns a derived piece of data from that state. For example, a selector might extract a specific item from an array, filter a list, or combine multiple pieces of state into a new object.
Why use Reselect?
1. Memoization for Performance: This is Reselect's primary benefit. It "memoizes" (caches) the result of a selector function. The selector will only re-run its computation if its input arguments (which are themselves the results of other "input selectors") have changed. If the inputs are the same as the last time, it returns the previously computed result without re-executing the potentially expensive computation. This is crucial for:
* Avoiding unnecessary re-renders: In React-Redux, if `mapStateToPros` or `useSelector` returns a new object *every time* (even if the underlying data is shallowly equal), it can trigger re-renders. Reselect helps ensure that components only re-render when the *relevant data* actually changes.
* Optimizing expensive computations: If you're filtering, sorting, or mapping large lists from your state, Reselect prevents these operations from running on every state update, only when the inputs they depend on change.
2. Composability: Selectors can be composed together. You can build complex selectors by combining simpler, more focused selectors. This promotes reusability and makes your state logic more modular and easier to test.
3. Encapsulation and Reusability: Selectors encapsulate the logic for deriving specific data. This logic can then be reused across different components or even different parts of the application without duplication.
4. Decoupling Components from State Shape: Components don't need to know the exact structure of the Redux state. They just ask for data via a selector, and the selector handles extracting and transforming it, making it easier to refactor your state shape in the future.
How Reselect Works (`createSelector`)
The core of Reselect is the `createSelector` function. It takes two types of arguments:
1. Input Selectors: One or more functions that extract specific slices of the state. These selectors are not memoized by `createSelector` themselves, but their results are used as inputs to the output selector.
2. Output Selector: A final function that receives the results of the input selectors as its arguments (in the same order they were provided). This is the function whose result is memoized.
`createSelector` creates a new selector instance. When this instance is called with the Redux state:
* It first calls all input selectors.
* It then checks if the results of the input selectors have changed (using a strict `===` comparison by default) since the last time.
* If *any* of the input results have changed, the output selector function is executed with the new input results, and its return value is cached.
* If *none* of the input results have changed, the output selector function is *not* executed, and the previously cached result is returned directly.
Integration with React-Redux
When using React-Redux, you typically use `useSelector` (for functional components) or `mapStateToProps` (for class components) to extract data from the Redux store. You pass your Reselect-created selectors directly to `useSelector` or `mapStateToProps`. This ensures that your components receive memoized data, preventing unnecessary re-renders.
Example Code
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';
import { createSelector } from 'reselect';
// --- 1. Redux Store Setup ---
// Reducers
const initialUsersState = [
{ id: 1, name: 'Alice', age: 30, isActive: true },
{ id: 2, name: 'Bob', age: 24, isActive: false },
{ id: 3, name: 'Charlie', age: 35, isActive: true },
{ id: 4, name: 'David', age: 28, isActive: false },
];
const initialFilterState = {
minAge: 25,
activeOnly: false,
};
function usersReducer(state = initialUsersState, action) {
switch (action.type) {
// Add actions if needed for user modification
default:
return state;
}
}
function filterReducer(state = initialFilterState, action) {
switch (action.type) {
case 'SET_MIN_AGE':
return { ...state, minAge: action.payload };
case 'TOGGLE_ACTIVE_ONLY':
return { ...state, activeOnly: !state.activeOnly };
default:
return state;
}
}
const rootReducer = combineReducers({
users: usersReducer,
filter: filterReducer,
});
// Create Store
const store = createStore(rootReducer);
// --- 2. Reselect Selectors ---
// Input Selectors: Extract specific slices of state
const getUsers = (state) => {
console.log('getUsers running...'); // This will run on every state update
return state.users;
};
const getMinAge = (state) => {
console.log('getMinAge running...'); // This will run on every state update
return state.filter.minAge;
};
const getActiveOnly = (state) => {
console.log('getActiveOnly running...'); // This will run on every state update
return state.filter.activeOnly;
};
// Memoized Selector: Combines input selectors to derive data
const getFilteredUsers = createSelector(
[getUsers, getMinAge, getActiveOnly], // Input selectors
(users, minAge, activeOnly) => { // Output selector (only runs if inputs change)
console.log('getFilteredUsers computation running...');
let filteredUsers = users.filter(user => user.age >= minAge);
if (activeOnly) {
filteredUsers = filteredUsers.filter(user => user.isActive);
}
return filteredUsers;
}
);
// Another memoized selector for a count
const getFilteredUsersCount = createSelector(
getFilteredUsers,
(filteredUsers) => {
console.log('getFilteredUsersCount computation running...');
return filteredUsers.length;
}
);
// --- 3. React Components using Selectors ---
function UserList() {
// Use the memoized selector
const filteredUsers = useSelector(getFilteredUsers);
const userCount = useSelector(getFilteredUsersCount);
console.log('UserList component rendering...'); // See when component re-renders
return (
<div>
<h2>Filtered Users ({userCount})</h2>
{filteredUsers.length === 0 ? (
<p>No users found matching criteria.</p>
) : (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>
{user.name} (Age: {user.age}, Status: {user.isActive ? 'Active' : 'Inactive'})
</li>
))}
</ul>
)}
</div>
);
}
function FilterControls() {
const dispatch = useDispatch();
const minAge = useSelector(getMinAge);
const activeOnly = useSelector(getActiveOnly);
const handleMinAgeChange = (e) => {
dispatch({ type: 'SET_MIN_AGE', payload: Number(e.target.value) });
};
const handleToggleActiveOnly = () => {
dispatch({ type: 'TOGGLE_ACTIVE_ONLY' });
};
return (
<div style={{ marginBottom: '20px', padding: '10px', border: '1px solid #ccc' }}>
<h3>Filter Controls</h3>
<div>
<label>
Minimum Age:
<input
type="number"
value={minAge}
onChange={handleMinAgeChange}
min="0"
/>
</label>
</div>
<div>
<label>
Show Active Only:
<input
type="checkbox"
checked={activeOnly}
onChange={handleToggleActiveOnly}
/>
</label>
</div>
</div>
);
}
function App() {
return (
<Provider store={store}>
<div style={{ fontFamily: 'Arial, sans-serif', padding: '20px' }}>
<h1>Reselect Example with React-Redux</h1>
<FilterControls />
<UserList />
<p>
<em>
Open your browser's console to see when selectors re-run and components re-render.
Notice how 'getFilteredUsers computation running...' only logs when
'Minimum Age' or 'Show Active Only' changes, not for every state update if filters stay the same.
</em>
</p>
</div>
</Provider>
);
}
export default App;








Reselect