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,
useEffectReducerandreact-use-bireducerare 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 Msgsystem 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:
- useEffectReducer version: useEffectReducer.stories.tsx → L121–203
- useReducer version: useEffectReducer.stories.tsx → L24–119
Related work
Excellent existing takes on bringing Elm-style "state + effects" reducers into React:
davidkpiano/useEffectReducer— by David Khourshid; archivedsoywod/react-use-bireducer— returns[state, effects]and processes effects through a separate effect reducer; archivedncthbrt/react-use-elmish— Elmish-style hook combining reducer logic with async helpersredux-loop— Redux enhancer adding Elm-like effect tuplesdai-shi/use-reducer-async— extendsdispatchfor async actionsuseReducerWithEmitEffect— Sophie Alpert's gist that inspired much of this work
Good Articles
- Christian Ekrem - "Chapter 2, Take 2: Why I Changed Course"
- David Khourshid – "Redux is half of a pattern (1/2)"
- David Khourshid – "There are so many fundamental misunderstandings about XState (and state machines in general)"
- David Khourshid – "No, disabling a button is not app logic."
- React docs – "You Might Not Need an Effect"