I built (another) Elm-style useEffectReducer hook for React

GitHub: bolasblack/react-components/tree/develop/packages/useEffectReducer

What is "Elm-style"?

Elm popularized the Model-View-Update pattern, where each update returns both the next model and the commands to run:

update : Msg -> Model -> ( Model, Cmd Msg )

This design keeps all logic about "what happens when event X occurs" in one place — the update function — instead of scattering side effects across random useEffects.

It also keeps reducers pure while making when and what side effects happen explicit and interpretable by a runtime.

React's docs echo this philosophy: effects should be an escape hatch, not the default — see You Might Not Need an Effect.

If you want to see how this kind of modeling makes UI logic elegant and maintainable, check out David Khourshid's classic post "No, disabling a button is not app logic."

So why another Elm-style reducer?

The author wanted a variant with different trade-offs:

  • Some are archived. For example, useEffectReducer and react-use-bireducer are now read-only.

  • Keep it tiny. Fits in one file with almost no dependencies — copy, tweak, or delete it whenever you want.

  • Effects as plain objects + separate interpreter. Returns serializable effect descriptors and implements the actual effect logic in one dedicated place. It's easier to test reducers (assert on descriptors) without invoking real side effects.

  • Lower the barrier. The goal is to make the Elm-style approach approachable without requiring deep knowledge of Elm's Cmd Msg system or learning a full-blown state machine library like XState.

Example usage

The article reimplements the example from David Khourshid's article "No, disabling a button is not app logic." using useEffectReducer.

Both implementations are available on GitHub:

Related work

Excellent existing takes on bringing Elm-style "state + effects" reducers into React:

Good Articles