Reselect in Redux
Reselect is a library for creating memoized selectors in Redux. It helps optimize performance by caching selector results and only recomputing when the input selectors return new values.
What is Reselect?
- Reselect is a library that provides a way to create memoized selectors.
- It caches the result of a selector and only recomputes when the input selectors return new values.
- Helps prevent unnecessary re-computations and improves performance.
- Built on top of the concept of “derived data” in Redux.
Installation:
npm install reselectHow does Reselect Work?
- Input Selectors: Define selectors that extract the data you need.
- Result Function: Define a function that computes the derived data from the input selectors.
- Memoization: Reselect caches the result and only recomputes when input selectors change.
- Performance: Avoids expensive computations on every state change.
Signature:
import { createSelector } from 'reselect';
const selector = createSelector(
[inputSelector1, inputSelector2, ...],
(result1, result2, ...) => computedResult
);Usage Examples
A basic memoized selector
import { createSelector } from 'reselect';
// Input selectors
const selectTodos = (state) => state.todos;
const selectFilter = (state) => state.filter;
// Memoized selector
const selectFilteredTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'completed':
return todos.filter(todo => todo.completed);
case 'active':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
);Composed selectors
import { createSelector } from 'reselect';
// Base selectors
const selectTodos = (state) => state.todos;
const selectFilter = (state) => state.filter;
// Memoized filtered todos
const selectFilteredTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'completed':
return todos.filter(todo => todo.completed);
case 'active':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
);
// Memoized stats (uses another memoized selector)
const selectTodoStats = createSelector(
[selectFilteredTodos],
(filteredTodos) => {
const total = filteredTodos.length;
const completed = filteredTodos.filter(todo => todo.completed).length;
const active = total - completed;
return {
total,
completed,
active,
completionRate: total > 0 ? (completed / total) * 100 : 0
};
}
);In components
import { useSelector } from 'react-redux';
import { selectFilteredTodos, selectTodoStats } from './selectors';
const TodoList = () => {
const filteredTodos = useSelector(selectFilteredTodos);
const stats = useSelector(selectTodoStats);
return (
<div>
<p>Total: {stats.total}</p>
<p>Completed: {stats.completed}</p>
<p>Active: {stats.active}</p>
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
};Selectors with parameters
import { createSelector } from 'reselect';
const selectTodos = (state) => state.todos;
// Factory function for parameterized selector
const makeSelectTodoById = () => createSelector(
[selectTodos, (state, todoId) => todoId],
(todos, todoId) => todos.find(todo => todo.id === todoId)
);
// Usage
const selectTodoById = makeSelectTodoById();
const todo = useSelector(state => selectTodoById(state, 123));Memoization Benefits
Performance improvement:
- Avoids expensive computations on every render.
- Only recomputes when dependencies actually change.
- Reduces unnecessary re-renders in React components.
Example without reselect:
// This runs on every state change
const selectExpensiveComputation = (state) => {
console.log('Computing expensive result...');
return state.todos
.filter(todo => !todo.completed)
.map(todo => ({ ...todo, priority: calculatePriority(todo) }))
.sort((a, b) => b.priority - a.priority);
};Example with reselect:
// This only runs when todos change
const selectExpensiveComputation = createSelector(
[selectTodos],
(todos) => {
console.log('Computing expensive result...');
return todos
.filter(todo => !todo.completed)
.map(todo => ({ ...todo, priority: calculatePriority(todo) }))
.sort((a, b) => b.priority - a.priority);
}
);Best Practices
- Use input selectors: Extract the data you need with simple selectors first.
- Keep result functions pure: Don’t have side effects in your selector functions.
- Compose selectors: Build complex selectors from simpler ones.
- Use factory functions: For selectors that need parameters.
- Test selectors: Ensure they work correctly with different state shapes.
A good selector
// Composable and memoized
const selectTodos = (state) => state.todos;
const selectFilter = (state) => state.filter;
const selectFilteredTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'completed':
return todos.filter(todo => todo.completed);
case 'active':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
);
const selectTodoCount = createSelector(
[selectFilteredTodos],
(filteredTodos) => filteredTodos.length
);A bad one
// Don't access state directly in createSelector
const selectFilteredTodos = createSelector(
[(state) => state.todos, (state) => state.filter], //
(todos, filter) => {
// Complex logic mixed with data access
return todos.filter(todo => {
if (filter === 'completed') return todo.completed;
if (filter === 'active') return !todo.completed;
return true;
});
}
);Common Interview Questions
Q: What is the difference between a regular selector and a reselect selector?
- Regular selectors run on every call, while reselect selectors are memoized and only recompute when dependencies change.
Q: When should you use reselect?
- Use reselect for expensive computations or when you want to prevent unnecessary re-renders.
Q: How does reselect determine when to recompute?
- Reselect uses shallow equality checks on the results of input selectors to determine if recomputation is needed.
Q: Can you use reselect with parameters?
- Yes, you can create factory functions that return parameterized selectors.
Q: What are the performance benefits of reselect?
- Avoids expensive computations, reduces unnecessary re-renders, and improves overall application performance.
Summary
- Reselect provides memoized selectors for Redux applications.
- It caches results and only recomputes when dependencies change.
- Use input selectors to extract data and result functions to compute derived data.
- Helps improve performance by avoiding expensive computations.
- Follow best practices for composable and maintainable selectors.
Interview angle 4
- “What does reselect do?” - memoizes derived state so an expensive computation or a newly-allocated object is not recreated on every store change. Without it, a selector returning
items.filter(...)returns a new array reference each call and re-renders every consumer. - “What was the cache-size-1 problem?” - the classic
createSelectormemoized only the last call, so two components using the same selector with different arguments thrashed it. Reselect 5 changed the default to a WeakMap-based memoizer, which handles that case; older advice tells you to build a selector factory per component instead. - “Do you need it for a plain field?” - no.
useSelector(s => s.user.name)returns a primitive compared by===; memoizing it adds cost for nothing. Reselect earns its place when the selector computes or allocates. - “Where should selectors live?” - next to the slice that owns the state, exported by name. Components importing a named selector rather than reaching into the state shape is what makes the shape refactorable.