Control Freak’s Friend: The State Reducer Pattern

Hand consumers the ability to override your component’s state transitions. Perfect for building reusable libraries where flexibility is key. Overkill for small apps but powerful for design systems.

The State Reducer Pattern allows a component to delegate state updates to a reducer function, giving external control over state changes while keeping internal logic consistent.

👉 Think of it like giving rules to a game — the component defines the logic, but the parent can tweak outcomes via a reducer.

Basics & Need

  • Useful when components have complex state transitions (e.g., multi-step forms, toggles with side effects).
  • Allows controlled behavior from parent without breaking component encapsulation.
  • Often paired with custom hooks for reusable components.

How to Achieve It

Step 1: Component with State Reducer

const Toggle = ({ initial = false, stateReducer }) => {
const [on, setOn] = React.useState(initial);

const internalSetOn = (newState) => {
if (stateReducer) {
const reducedState = stateReducer(on, newState);
setOn(reducedState);
} else {
setOn(newState);
}
};

return <button onClick={() => internalSetOn(!on)}>{on ? "ON" : "OFF"}</button>;
};

Step 2: Using the Reducer

const App = () => {
const customReducer = (current, next) => (current && next ? false : next);

return <Toggle stateReducer={customReducer} />;
};

👉 Parent can intercept and control state updates via stateReducer.

Best Practices

  • Keep reducer pure: output depends only on inputs.
  • Provide default behavior in component for uncontrolled usage.
  • Use with custom hooks to share logic across multiple components.
  • Document state transitions clearly for consumers.

Real-World Examples

  1. Toggle Buttons → Prevent multiple toggles at once.
  2. Dropdown Menus → Limit open states or control selection externally.
  3. Stepper Forms → Parent controls which step is allowed next.

Advantages

  • Allows flexible control by parent components.
  • Keeps internal logic consistent while supporting customization.
  • Useful in component libraries where consumers may want to override behavior.

Disadvantages

  • Adds complexity for simple components.
  • Overusing reducers for trivial state → unnecessary boilerplate.
  • Debugging can be tricky if multiple layers control the state.

Common Problems / Pitfalls

  • Reducer returning invalid state → component behaves unexpectedly.
  • Forgetting to provide default behavior for uncontrolled usage.
  • Confusing internal vs external state logic → hard to maintain.

Summary Recommendation

Use the State Reducer Pattern when you want a component to expose state control to parent components without losing internal logic. It’s ideal for reusable components and component libraries. For simpler cases, standard state or custom hooks may suffice.