← Back to all hooks

useReducerWithEffects

Reducer with side effects support. Perfect for finite state machines and multi-step workflows.

Live Demo

📋 Multi-Step Form

Step 1
Step 2
Step 3

Personal Information

📝 Event Log

No events yet

💡 How It Works:

  • Reducer manages state transitions
  • Effects handle side effects (API calls, timers)
  • Perfect for finite state machines
  • Multi-step workflows made easy
  • Automatic cleanup on unmount

Code Examples

Basic Usage

import { useReducerWithEffects } from "@uiblock/hooks";

const [state, dispatch] = useReducerWithEffects({
  reducer: (state, action) => {
    // Handle state transitions
  },
  effects: {
    SUBMIT: (state, action, dispatch) => {
      // Side effect: API call
      fetch('/api/submit', { method: 'POST', body: JSON.stringify(state) })
        .then(() => dispatch({ type: 'SUCCESS' }))
        .catch(() => dispatch({ type: 'ERROR' }));
    }
  }
}, initialState);

Finite State Machine

import { createStateMachine, useReducerWithEffects } from "@uiblock/hooks";

const machine = createStateMachine({
  initial: 'idle',
  context: { retries: 0 },
  states: {
    idle: {
      on: { FETCH: 'loading' }
    },
    loading: {
      on: { SUCCESS: 'success', ERROR: 'error' },
      effect: (context, event, send) => {
        fetch('/api/data')
          .then(() => send('SUCCESS'))
          .catch(() => send('ERROR'));
      }
    },
    success: {
      on: { RESET: 'idle' }
    },
    error: {
      on: { RETRY: 'loading', RESET: 'idle' }
    }
  }
});

const [state, dispatch] = useReducerWithEffects(machine, machine.initialState);

How It Works

Here's the implementation:

import { useReducer, useEffect, useRef, Reducer } from 'react'

export type Action<T = string, P = any> = {
  type: T
  payload?: P
}

export type Effect<S, A> = (
  state: S,
  action: A,
  dispatch: (action: A) => void
) => void | (() => void)

export function useReducerWithEffects<S, A extends Action>(
  config: {
    reducer: Reducer<S, A>
    effects?: Record<string, Effect<S, A>>
  },
  initialState: S
): [S, (action: A) => void] {
  const { reducer, effects = {} } = config
  const [state, dispatch] = useReducer(reducer, initialState)
  const effectCleanupRef = useRef<(() => void) | void>()

  useEffect(() => {
    return () => {
      if (effectCleanupRef.current) {
        effectCleanupRef.current()
      }
    }
  }, [])

  const dispatchWithEffects = (action: A) => {
    // Cleanup previous effect
    if (effectCleanupRef.current) {
      effectCleanupRef.current()
      effectCleanupRef.current = undefined
    }

    // Dispatch action
    dispatch(action)

    // Run effect if exists
    const effect = effects[action.type]
    if (effect) {
      setTimeout(() => {
        effectCleanupRef.current = effect(state, action, dispatchWithEffects)
      }, 0)
    }
  }

  return [state, dispatchWithEffects]
}

💡 Use Cases

  • Multi-step forms and wizards
  • Finite state machines
  • Complex workflows with side effects
  • Authentication flows
  • Checkout processes