DEV Community

c4605
c4605

Posted on

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

🌳 What is “Elm-style”?

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

update : Msg -> Model -> ( Model, Cmd Msg )
Enter fullscreen mode Exit fullscreen mode

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?

I know there are several similar libraries — and I like many of them! — but I 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. I prefer returning serializable effect descriptors and implementing the actual effect logic in one dedicated place. It’s easier to test reducers (assert on descriptors) without invoking real side effects. (Elm’s update : Msg -> Model -> (Model, Cmd Msg) inspires this split.)

    Of course, useEffectReducer doesn’t stop you from returning a function as an effect — the API is flexible enough; it’s just not my personal preference 🙂.

  • 💬 Lower the barrier. I don’t want people curious about the Elm-style approach to feel like they must first study Elm’s Cmd Msg system or learn a full-blown state machine library like XState just to try this pattern.

    Both XState and Elm are amazing — I’m genuinely thankful to David Khourshid and all of XState’s contributors, and to the Elm community. I’ve used XState in multiple real projects (it’s saved me countless times!), but it does have a learning curve. Moving from useReducer to useEffectReducer, on the other hand, should feel smooth and approachable 🚀.)

🧪 Example usage

Let’s reimplement the example from David Khourshid’s article
“No, disabling a button is not app logic.”, but this time using useEffectReducer

You can also check out both implementations on GitHub:

🔗 Related work

Here are some excellent existing takes on bringing Elm-style “state + effects” reducers into React:

Top comments (0)