Reselect

repository·master·Indexed 12 days ago

https://github.com/reduxjs/reselect

A library for creating memoized selector functions to compute derived data efficiently from immutable state, commonly used with Redux to optimize UI updates and minimize state storage. Version 5.2.0 features include createSelector, createStructuredSelector, and createSelectorCreator for custom memoization strategies, as well as TypeScript support via .withTypes() for pre-typing state.

Tokens
25.3K
Snippets
63
Records
93
Agent score
96%

What's inside Reselect

  1. Configure `inputStabilityCheck` for development

    master

    Reselect includes a development-only check to ensure that input selectors do not return a new reference on every call. If an input selector returns a new reference (e.g., using .filter() or .map() inside the input selector), memoization will fail.

    When enabled, Reselect runs the input selectors twice during a call and logs a warning if the results differ. This check is automatically disabled in production.

    Available frequencies for inputStabilityCheck:

    • once: Run only the first time the selector is called (default).
    • always: Run every time the selector is called.
    • never: Never run the check.
    // Example of an unstable input selector that triggers the check:
    const selectCompletedTodosLength = createSelector(
      [
        (state: RootState) => state.todos.filter(({ completed }) => completed === true)
      ],
      completedTodos => completedTodos.length,
      { devModeChecks: { inputStabilityCheck: 'always' } }
    )
  2. How createSelector works

    master

    Reselect provides a createSelector API to generate memoized selector functions.

    How it works

    1. Input Selectors: You provide one or more input selectors that extract specific values from the arguments (usually the state).
    2. Result Function: You provide a function that receives the values returned by the input selectors and computes a derived value.
    3. Memoization: The output selector only recomputes the result if the values returned by the input selectors change. If the inputs are the same, it returns the existing result reference.

    This reference stability is critical for performance in libraries like React and React-Redux, as it allows them to skip unnecessary re-renders via reference equality checks.

    import { createSelector } from 'reselect'
    
    const memoizedSelector = createSelector(
      [inputSelector1, inputSelector2], // Input selectors (dependencies)
      (val1, val2) => {
        // Result function: computes derived data from val1 and val2
        return val1 + val2
      }
    )
  3. Configure `identityFunctionCheck` for development

    master

    Reselect provides a development-only check to ensure a clear separation between Extraction Logic and Transformation Logic.

    • Extraction Logic (should be in input selectors): Retrieving data (e.g., state => state.todos).
    • Transformation Logic (should be in the result function): Manipulating data (e.g., todos => todos.map(t => t.id)).

    If the result function of a selector is an identity function (it simply returns the input without transforming it), it is considered a misuse of createSelector because it provides no benefit to memoization. This check is automatically disabled in production.

    Available frequencies for identityFunctionCheck:

    • once: Run only the first time the selector is called (default).
    • always: Run every time the selector is called.
    • never: Never run the check.
    // ❌ Incorrect Use Case: Result function is an identity function
    const brokenSelector = createSelector(
      [(state: RootState) => state.todos],
      todos => todos, // This is an identity function
      { devModeChecks: { identityFunctionCheck: 'always' } }
    )
  4. Use `weakMapMemoize` for infinite cache size

    master

    weakMapMemoize creates a tree of WeakMap-based cache nodes based on the identity of the arguments passed to the function. This provides an effectively infinite cache size because results are kept in memory as long as references to the arguments exist, and are automatically cleared when the arguments are garbage-collected.

    Design Tradeoffs

    • Pros: Effectively infinite cache size without manual configuration.
    • Cons: No control over cache duration (relies on garbage collection) and argument comparisons are strictly based on strict reference equality.
  5. Understand Reselect terminology

    master

    To use Reselect effectively, understand these core concepts:

    • Selector Function: A function that accepts one or more JavaScript values (like a Redux state) and derives a result.
    • Input Selectors (Dependencies): Basic selector functions passed as the first argument(s) to createSelector. They extract the specific pieces of data needed from the arguments.
    • Output Selector: The memoized selector function returned by createSelector.
    • Result Function: The function provided to createSelector that receives the return values of the input selectors as its arguments and returns the final derived value.
  6. How Reselect's cascading memoization works

    master

    Reselect uses a two-stage "cascading" approach to memoization that makes it more efficient than standard memoization, especially in environments like Redux where the root state reference changes frequently.

    The Two-Stage Process

    1. Initial Run: Reselect executes all inputSelectors, gathers their results, and passes them to the resultFunc.
    2. Subsequent Runs:
      • First Level (Argument Check): Reselect compares the current arguments with the previous ones (using argsMemoize). If the arguments are identical, it returns the cached result immediately without running any selectors.
      • Second Level (Input Selector Check): If the arguments have changed, Reselect runs the inputSelectors and compares their current results with the previous ones (using memoize).
        • If all inputSelectors return the same results as the previous run, Reselect returns the cached result and skips running the resultFunc.
        • If any inputSelector returns a different result, Reselect runs the resultFunc with the new values.

    Note: If any single inputSelector returns a different result, all inputSelectors will recalculate during that stage.

    // Conceptual representation of the internal orchestration
    const finalSelector = (...args) => {
      const extractedValues = inputSelectors.map(inputSelector =>
        inputSelector(...args)
      )
      return resultFunc(...extractedValues)
    }
  7. Share selectors across multiple component instances

    master

    If you are sharing a selector across multiple component instances that pass in different arguments, standard memoization might fail to be effective. To ensure consistent memoization behavior:

    • If using lruMemoize, pass a larger maxSize.
    • Use weakMapMemoize (available as of 5.0.0+).
    // Use weakMapMemoize for better handling of different arguments across instances
    import { weakMapMemoize } from 'reselect';
    
    const mySelector = createSelector(
      [inputSelector],
      resultFunction,
      { memoize: weakMapMemoize }
    );
  8. Use Re-reselect to enhance selector memoization

    master
    If you need to reduce selector recalculations when the same selector is repeatedly called with one or a few different arguments, use re-reselect. It enhances Reselect selectors by wrapping createSelector and returning a memoized collection of selectors indexed by a cache key generated from a custom resolver function.
  9. Reselect Terminology

    master

    Understanding the core concepts of Reselect:

    • Selector Function: A function that accepts one or more JavaScript values as arguments and derives a result. In Redux, the first argument is typically the state.
    • Input Selectors (also called Dependencies): Basic selector functions used as building blocks. They are passed as the first argument(s) to createSelector and are responsible for extracting values for the result function.
    • Output Selector: The actual memoized selector created by createSelector.
    • Result Function: The function provided to createSelector after the input selectors. It receives the return values of the input selectors as arguments and returns the final derived result.
  10. How to use createSelector for memoized selectors

    master

    The createSelector API generates memoized selector functions. It works by taking one or more input selectors (which extract values from arguments) and a result function (which computes the derived data using those extracted values).

    Key Benefits:

    • Efficiency: The result function is only re-executed if the return values of the input selectors change.
    • Reference Equality: If the inputs haven't changed, createSelector returns the exact same reference as the previous call. This is critical for optimizing performance in libraries like React and React-Redux by preventing unnecessary re-renders.

    Basic Pattern

    const outputSelector = createSelector(
      [inputSelector1, inputSelector2], // Dependencies/Input Selectors
      resultFunc // Function that computes the derived value
    )
    import { createSelector } from 'reselect'
    
    interface RootState {
      todos: { id: number; completed: boolean }[]
      alerts: { id: number; read: boolean }[]
    }
    
    const state: RootState = {
      todos: [
        { id: 0, completed: false },
        { id: 1, completed: true }
      ],
      alerts: [
        { id: 0, read: false },
        { id: 1, read: true }
      ]
    }
    
    // A standard selector (not memoized)
    const selectCompletedTodos = (state: RootState) => {
      console.log('selector ran')
      return state.todos.filter(todo => todo.completed === true)
    }
    
    // A memoized selector using createSelector
    const memoizedSelectCompletedTodos = createSelector(
      [(state: RootState) => state.todos],
      todos => {
        console.log('memoized selector ran')
        return todos.filter(todo => todo.completed === true)
      }
    )
    
    // First call: runs the result function
    memoizedSelectCompletedTodos(state) // "memoized selector ran"
    
    // Subsequent calls with same state: returns cached result without running result function
    memoizedSelectCompletedTodos(state)
    
    // Comparison with non-memoized selector
    console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) // false (new array reference)
    console.log(memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state)) // true (same reference)