frontend / state managers / redux / selectors_in_redux.md

Selectors in Redux

3 interview angles 4 min read source

Selectors in Redux

Selectors are functions that extract and compute data from the Redux store state. They help encapsulate state access logic and can be used to derive computed data from the store.

Table of Contents


What are Selectors?

  • Selectors are functions that take the Redux state and return derived data.
  • They encapsulate state access logic and can compute derived values.
  • Used to avoid duplicating state access logic across components.
  • Can be memoized for performance optimization.

Basic selector:

const selectTodos = (state) => state.todos;
const selectCompletedTodos = (state) => state.todos.filter(todo => todo.completed);

How do Selectors Work?

  1. State Access: Selectors receive the entire Redux state as an argument.
  2. Data Extraction: They extract specific pieces of data from the state.
  3. Computation: They can compute derived values (filtering, mapping, etc.).
  4. Return Value: They return the extracted or computed data.

Flow:

Redux State → Selector → Derived Data → Component

Types of Selectors

1. Basic Selectors:

  • Simple functions that extract data from state.
  • No computation, just data access.

2. Computed Selectors:

  • Functions that compute derived data from state.
  • Can filter, map, reduce, or combine data.

3. Memoized Selectors:

  • Selectors that cache their results for performance.
  • Only recompute when dependencies change.

Usage Examples

Basic selectors:

// Simple data extraction
const selectTodos = (state) => state.todos;
const selectUser = (state) => state.user;
const selectFilter = (state) => state.filter;

Computed selectors:

// Filtering data
const selectCompletedTodos = (state) =>
  state.todos.filter(todo => todo.completed);

const selectActiveTodos = (state) =>
  state.todos.filter(todo => !todo.completed);

// Computing statistics
const selectTodoStats = (state) => {
  const todos = state.todos;
  const total = todos.length;
  const completed = todos.filter(todo => todo.completed).length;
  const active = total - completed;

  return {
    total,
    completed,
    active,
    completionRate: total > 0 ? (completed / total) * 100 : 0
  };
};

Using selectors in components:

import { useSelector } from 'react-redux';

const TodoList = () => {
  const todos = useSelector(selectTodos);
  const completedTodos = useSelector(selectCompletedTodos);
  const stats = useSelector(selectTodoStats);

  return (
    <div>
      <p>Total: {stats.total}</p>
      <p>Completed: {stats.completed}</p>
      <p>Active: {stats.active}</p>
    </div>
  );
};

Using selectors with connect:

import { connect } from 'react-redux';

const mapStateToProps = (state) => ({
  todos: selectTodos(state),
  completedTodos: selectCompletedTodos(state),
  stats: selectTodoStats(state)
});

export default connect(mapStateToProps)(TodoList);

Best Practices

  • Keep selectors pure: They should not have side effects.
  • Use descriptive names: Make selector names clear about what they return.
  • Compose selectors: Build complex selectors from simpler ones.
  • Memoize expensive selectors: Use reselect for performance optimization.
  • Keep selectors focused: Each selector should have a single responsibility.

Good example:

// Composable selectors
const selectTodos = (state) => state.todos;
const selectFilter = (state) => state.filter;

const selectFilteredTodos = (state) => {
  const todos = selectTodos(state);
  const filter = selectFilter(state);

  switch (filter) {
    case 'completed':
      return todos.filter(todo => todo.completed);
    case 'active':
      return todos.filter(todo => !todo.completed);
    default:
      return todos;
  }
};

Bad example:

// Don't mix concerns
const selectTodosAndUser = (state) => ({
  todos: state.todos,
  user: state.user,
  // This selector does too many things
  stats: computeStats(state.todos),
  settings: state.settings
});

Common Interview Questions

Q: Why use selectors instead of accessing state directly?

  • Selectors encapsulate state access logic, making it reusable and easier to maintain.

Q: What is the difference between basic and computed selectors?

  • Basic selectors just extract data, while computed selectors derive new data from existing state.

Q: How do you optimize selector performance?

  • Use memoization (e.g., with reselect) to cache results and only recompute when dependencies change.

Q: Can selectors have side effects?

  • No, selectors should be pure functions without side effects.

Q: How do you test selectors?

  • Test selectors by passing different state objects and verifying the returned values.

Summary

  • Selectors are functions that extract and compute data from Redux state.
  • They help encapsulate state access logic and can derive computed data.
  • Use basic selectors for simple data extraction and computed selectors for derived data.
  • Memoize expensive selectors for better performance.
  • Keep selectors pure and focused on a single responsibility.

Interview angle

  • “Why use a selector instead of reading state directly?” - it encapsulates the state shape. Every component that reaches into state.a.b.c has to change when the shape does; a named selector is one place.
  • “What causes a useSelector to re-render constantly?” - returning a new reference. useSelector compares with ===, so returning an object, an array, or the result of .map/.filter fails every time. Select primitives, pass shallowEqual, or memoize with reselect.
  • “Where should derivation happen?” - in the selector, not the reducer. Storing derived data duplicates the source of truth and creates the possibility of the two disagreeing.