liveBook · Manning

Reduction operation

As explained in React in Depth

A reducer is a function that computes a new state from the current state and an action describing a requested change.

A reducer takes current state and an action describing a requested change, then returns the next state. In React, useReducer connects that transition function to component state. Discriminated-union actions make operations explicit; generic reducers let the same state logic work across different element types. section 6.1.5 section 6.5

Reordering a playlist

A generic reorder reducer accepts an array and actions such as moveUp or moveToPosition. A useReorderable hook wraps it with useReducer and exposes operations to a playlist component. TypeScript may need an explicit Reducer<State<T>, Action> argument when useReducer loses the generic type; React 19 removes that workaround. section 6.1.5

Listing 6.2 The useReorderable hook
import { useReducer } from "react";
type State<T> = T[];
type Action =
  | { type: "moveUp"; index: number }
  | { type: "moveDown"; index: number }
  | { type: "moveToTop"; index: number }
  | { type: "moveToBottom"; index: number }
  | { type: "moveToPosition"; index: number; targetIndex: number };
function reorder<T>(state: State<T>, action: Action): State<T> {
  const newState: T[] = [...state];
  switch (action.type) {
    case "moveUp": {
      if (action.index <= 0 || action.index >= newState.length) {
        return state;
      }
      // Swap previous element with index
      [newState[action.index - 1], newState[action.index]] = [
        newState[action.index],
        newState[action.index - 1],
      ];
      return newState;
    }
    case "moveDown": {
      if (action.index < 0 || action.index >= newState.length - 1) {
        return state;
      }
      // Swap next element with index
      [newState[action.index], newState[action.index + 1]] = [
        newState[action.index + 1],
        newState[action.index],
      ];
      return newState;
    }
    case "moveToTop": {
      if (action.index <= 0 || action.index >= newState.length) {
        return state;
      }
      // Move index to top
      const itemToTop = newState.splice(action.index, 1)[0];
      newState.unshift(itemToTop);
      return newState;
    }
    case "moveToBottom": {
      if (action.index < 0 || action.index >= newState.length) {
        return state;
      }
      // Move index to bottom
      const itemToBottom = newState.splice(action.index, 1)[0];
      newState.push(itemToBottom);
      return newState;
    }
    default:
      return state;
  }
}
export function useReorderable<T>(initial: State<T>) {
  const [state, dispatch] = useReducer<
    Reducer<State<T>, Action>
  >(reorder, initial);
  return {
    list: state,
    moveUp: (index: number) =>     #1
      dispatch({ type: "moveUp", index }),   #1
    moveDown: (index: number) =>    #1
      dispatch({ type: "moveDown", index }),   #1
    moveToTop: (index: number) =>   #1
      dispatch({ type: "moveToTop", index }),  #1
    moveToBottom: (index: number) =>   #1
      dispatch({ type: "moveToBottom", index }),    #1
  };
}

Inspect the complete hook implementation, including its action union and array-reordering cases. section 6.1.5

Reducers also organize shared application data: a provider can expose state and callbacks through context while reducer actions update a combined state object. One implementation wraps reducer cases with Immer and persists the state in local storage. section 8.4.2

How React in Depth covers it

How deep it goes
Works through generic reducer typing and a reorder-hook implementation, noting the React 19 typing change.
Examples
Uses a reorderable song playlist and shared data for a list of things.
What you can do after
Build a generic reorderable-list hook with a typed reducer and expose its actions to a playlist component.
How it connects
Connects useReducer with TypeScript generics, discriminated unions, React context, and Immer.

Also explained in

Compare how 9 books explain reduction operation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of React in Depth. The section links point to the parts of the book it draws on.