Suggest an editImprove this articleRefine the answer for “What are selectors in Redux?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Redux selectors** are pure functions that read data from the store state. A plain selector reruns on every render; `createSelector` from Reselect caches the result and skips recomputation if inputs have not changed. Use Reselect for any derived data like filtered lists or computed totals.Shown above the full answer for quick recall.Answer (EN)Image**Selectors in Redux** are pure functions that read specific data from the store state and optionally transform it before a component uses it. ## Theory ### TL;DR - Selector: a function that takes `state` and returns a slice of it - Plain selectors recompute on every render; memoized ones (Reselect) skip the work if inputs have not changed - Use `createSelector` for any derived data: filtered lists, computed totals, sorted arrays - Think of it like a restaurant menu: you order exactly what you need, not the whole kitchen inventory ### Quick example ```javascript // Plain selector - runs on every render const getUsers = (state) => state.users; // Memoized selector - only recalculates if users array reference changes import { createSelector } from 'reselect'; const getActiveUsers = createSelector( [getUsers], (users) => users.filter(u => u.active) ); // In a component const activeUsers = useSelector(getActiveUsers); // Returns [{id:1, active:true}] - cached until users reference changes ``` After the first call, `getActiveUsers` stores the result. On the next render, if `state.users` is the same reference, it returns the cached array without running `.filter()` again. ### Plain vs memoized A plain selector runs every time `useSelector` triggers, which happens on every store update. For raw state access that is fine. But the moment you add `.filter()`, `.map()`, or any computation, that work runs on every single render, even if nothing in that slice changed. I once tracked a component re-rendering 200 times per second in a production dashboard. The culprit was a single selector returning a new array on every call even though the source data had not moved. Reselect's `createSelector` wraps the computation in a cache keyed by the output of input selectors. Shallow equality check on those inputs. If the check passes, it returns the cached value. That turns repeated O(n) work into a near-instant cache hit. ### When to use - **Raw state access** (single field, no transformation): plain selector, no library needed - **Derived or computed values** (filtered lists, sums, sorted arrays): memoized with Reselect - **Same data in multiple components**: shared memoized selector keeps logic consistent - **Large state tree with frequent renders**: memoized to stop cascading re-renders ### How Reselect works internally `createSelector` takes an array of input selectors and a result function. On each call it runs the input selectors, then compares their outputs to the previous run using reference equality. If all outputs match, it skips the result function and returns the cached value. If any input returns a new reference, it reruns the result function and caches the new output. One catch: the default cache size is 1. Each selector stores only the last result. For selectors that handle multiple instances, like per-item selectors in a list, use the factory pattern instead: `const makeUserSelector = (id) => createSelector([getUsers], users => users[id])`. ### Common mistakes **1. Mutating state inside a selector** ```javascript // Wrong - breaks purity and reference equality const getProcessed = createSelector([getUsers], (users) => { users[0].name = 'mutated'; return users; }); // Correct - return a new reference const getProcessed = createSelector([getUsers], (users) => users.map(u => ({ ...u, processed: true })) ); ``` Mutation breaks the reference equality check that memoization depends on. Result: stale data or missed re-renders. **2. Missing input selectors** ```javascript // Wrong - getPosts bypasses the cache entirely createSelector([getUsers], getPosts, (users, posts) => ...); // Correct - list all inputs in the array createSelector([getUsers, getPosts], (users, posts) => ...); ``` Reselect tracks only the inputs listed in the array. Extras get passed as arguments but are not watched. **3. Creating a selector inside a component** ```javascript // Wrong - new selector instance on every render, cache resets constantly const MyComponent = () => { const getFiltered = createSelector( [getUsers], u => u.filter(x => x.active) ); const users = useSelector(getFiltered); }; // Correct - define outside the component const getFilteredUsers = createSelector( [getUsers], u => u.filter(x => x.active) ); ``` **4. Skipping shallowEqual for object selectors** ```javascript // May cause unnecessary re-renders if selector returns new object each time useSelector(state => state.complexObject); // Better import { shallowEqual } from 'react-redux'; useSelector(state => state.complexObject, shallowEqual); ``` `useSelector` uses reference equality by default. A selector that returns a new object on every call (even with identical values) will trigger a re-render. ### Real-world usage - E-commerce: `useSelector(getCartTotal)` computes the cart sum without repeating logic across components - Todo apps: `getVisibleTodos` with a visibility filter (the official Redux examples repo uses this exact pattern) - Normalized stores: select entities by ID, joining related data from multiple slices - Zustand for small apps with under 10 selectors; Redux plus Reselect for normalized stores with complex derived state ### Follow-up questions **Q:** What happens if you return a new array from `useSelector` without Reselect? **A:** The component re-renders on every store update even if the data is identical. A new array reference fails the equality check that `useSelector` applies by default. **Q:** How does selector composition work? **A:** The output of one selector becomes an input to the next. `createSelector([getVisible], getCount)` memoizes independently at each level. A change in the source bubbles up only through selectors that depend on it. **Q:** When does a memoized selector recompute? **A:** When any input selector returns a new reference. Shallow equality on arrays and objects means mutating in place will not trigger a recompute. That is a common bug when updating state without immer. **Q:** (Senior) How do selectors in RTK Query differ from Reselect? **A:** RTK Query auto-generates tag-based selectors tied to cache entries, including loading and error states. Keep Reselect for domain logic outside API queries, like filtering or sorting already-cached data. ## Examples ### Basic: filtering active users ```javascript import { createSelector } from 'reselect'; import { useSelector } from 'react-redux'; // Input selector const getUsers = (state) => state.users; // Derived selector const getActiveUsers = createSelector( [getUsers], (users) => users.filter(u => u.active) ); function ActiveUserList() { const activeUsers = useSelector(getActiveUsers); return ( <ul> {activeUsers.map(u => <li key={u.id}>{u.name}</li>)} </ul> ); } // If state.users reference does not change, returns cached result ``` The component only re-renders when the `users` array in the store actually changes, not on every dispatch. ### Intermediate: todo app with visibility filter ```javascript const getTodos = (state) => state.todos; const getVisibilityFilter = (state) => state.visibilityFilter; const getVisibleTodos = createSelector( [getTodos, getVisibilityFilter], (todos, filter) => { switch (filter) { case 'SHOW_COMPLETED': return todos.filter(t => t.completed); case 'SHOW_ACTIVE': return todos.filter(t => !t.completed); default: return todos; } } ); // In component const todos = useSelector(getVisibleTodos); // Recomputes only if todos array or filter string changes // Both inputs are tracked independently ``` This pattern from the official Redux todos example shows why selectors matter. Without `getVisibleTodos`, each component would duplicate the filter logic or re-run it on every render regardless of what actually changed.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.