Pattern

State Reducer Pattern

A state reducer is a pure function that takes an action and current state, then returns either an error or a new state. This pattern provides a deterministic, testable way to handle state transitions in collaborative systems.

Part 1: The State Reducer Pattern

Overview

The State Reducer Pattern is a functional approach to state management where a state reducer function is responsible for computing the next state based on the current state and an action. The key characteristic is that the reducer is pure: given the same inputs, it always produces the same output, making it predictable and testable.

In collaborative systems, reducers provide a clean way to handle state transitions while enforcing business rules, validating parameters, and checking guards. Instead of scattering validation logic throughout your codebase, the reducer centralizes it in one place.

A note on terminology

The term "reducer" has existed for a long time in functional programming to describe a function that reduces a list of values to a single output. This is the classic fold/reduce operation found in many programming languages.

However, what we're referring to in this pattern is the state reducer concept that was introduced with Redux, the state management library for React. In Redux, a reducer is a pure function that takes the current state and an action, and returns a new state. This pattern has since been adopted widely beyond React applications.

The State-Based Collaboration Framework uses this state reducer pattern as the core mechanism for handling state transitions, adapted for collaborative systems where reducers must also handle validation, guards, and explicit error types.

When to use this pattern

Use the State Reducer Pattern when you have:

  • Complex state transitions: Multiple conditions, validations, and business rules that determine the next state
  • Need for determinism: You require predictable, testable state changes that always produce the same result for the same inputs
  • Validation requirements: Actions need parameter validation, guard checks, and transition eligibility verification
  • Error handling: You want explicit error types (transition not allowed, invalid parameters, guard failures) rather than exceptions
  • Testability: You need to easily test state transitions in isolation without side effects

This pattern is particularly useful in event-sourced systems, state machines, and any system where state transitions need to be deterministic and auditable.

Core concepts

Reducer function

A pure function with the signature: (action, currentState) → Result. The result is either a new state or an error. The reducer never mutates the current state; it always returns a new state object.

Actions

Actions represent user intent or system events. They contain the type of operation and any parameters needed. Examples: SubmitDocument, ApproveCase, UpdateStatus.

Error types

The reducer can return three types of errors:

  • Transition/Action not allowed: The action is not permitted in the current state, or the user doesn't have permission to perform it
  • Incorrect parameters: The action parameters are invalid, missing required fields, or don't meet validation rules
  • Guard error: A guard condition (precondition) failed. The transition is allowed, but some condition must be true first (e.g., "document must be complete before submission")

Result type

The reducer returns a discriminated union: either Success(newState) or Error(errorType, message). This makes error handling explicit and type-safe.

Example: Document submission reducer

Consider a document workflow where documents can be submitted for review:

State: { status: "Draft", content: "...", requiredFields: [...] }
Action: SubmitDocument(userId: "alice")
Reducer logic:
  1. Check if transition is allowed: Draft → Submitted ✓
  2. Validate parameters: userId is present ✓
  3. Check guard: all requiredFields are filled? → Error if not
  4. If all checks pass: return new state with status: "Submitted"
Possible results:
  • Success: { status: "Submitted", ... }
  • Guard error: "Document must have all required fields filled before submission"
  • Transition error: "Cannot submit document in current state" (if already submitted)

Benefits

  • Deterministic: Same inputs always produce same outputs, making behavior predictable
  • Testable: Pure functions are easy to test in isolation without mocks or side effects
  • Explicit errors: Error types are part of the function signature, making error handling clear and type-safe
  • Centralized logic: All transition logic lives in one place, making it easier to understand and maintain
  • Composable: Reducers can be composed or chained for complex workflows
  • Replayable: Since reducers are pure, you can replay actions to reconstruct state, useful for debugging and event sourcing

Part 2: Reducers in the State-Based Collaboration Framework

How reducers fit in the framework

In the State-Based Collaboration Framework, reducers are the core mechanism for implementing the Transitions component. Each transition in your state machine is implemented as a reducer function that takes an action and the current state, then returns either a new state or an error.

The framework uses reducers to:

  • Validate that transitions are allowed in the current state
  • Check action-control policies to ensure the user has permission
  • Validate action parameters before processing
  • Evaluate guard conditions that must be true for the transition
  • Compute the new state deterministically

Error types in the framework

The framework defines three explicit error types that reducers can return:

Transition/Action not allowed

This error occurs when the action is not permitted in the current state, or when action-control policies prevent the user from performing the action. For example, trying to approve a document that is still in "Draft" state, or a non-reviewer attempting to approve.

Incorrect parameters

This error occurs when the action parameters are invalid, missing required fields, or don't meet validation rules. For example, submitting a document without a required title, or providing an invalid date format.

Guard error

This error occurs when a guard condition (precondition) fails. The transition is allowed in principle, but some condition must be true first. For example, a document must have all required fields filled before it can be submitted, or a case must have an assignee before it can be closed.

Related patterns

This pattern works well with:

Explore more patterns

This pattern is one of many for building collaborative systems. Check out other patterns or dive deeper into the framework components.