Ecosystem ยท Sep 1, 2017

ReasonReact 0.2.4: Reducers are here!

Cheng Lou
Core Team Member

What's new?

v0.2.4 was done in collaboration with the ReactJS team.

This is a release we've been planning for quite a while now, and is one that embodies what the Reason team is trying to achieve through ReasonReact.

First, a bit of context: if you use ReactJS, then you've likely heard of the the massively popular Redux state management library. Redux's original inspiration comes from Elm, whose root we share.

It's only appropriate then, that in ReasonReact, we leverage the Reason language's existing facilities (notably, variant and pattern-matching) to model a lightweight, low-surface data management API that we now provide out-of-the-box. Here it is:

RE
type actions = | BumpCount | ToggleDialog bool; type state = {count: int, showDialog: bool}; let component = ReasonReact.reducerComponent "MyComponent"; let make ::className _children => { ...component, initialState: fun () => {count: 0, showDialog: false}, reducer: fun action state => switch action { | BumpCount => ReasonReact.Update {...state, count: state.count + 1} | ToggleDialog show => ReasonReact.Update {...state, showDialog: show} }, render: fun {state, reduce} => { let message = "You've clicked " ^ string_of_int state.count ^ " time(s)"; <div className> <button onClick=(reduce (fun _ => BumpCount))> (ReasonReact.stringToElement message) </button> <MyButton onToggle=(reduce (fun toggleState => ToggleDialog toggleState)) /> (state.showDialog ? <Dialog /> : ReasonReact.nullElement) </div> } };

More examples at reason-react-example.

That's it! A reducer function on your component, that pattern-matches on the possible state transforms, and specifies what the next state should be. This is akin to a state machine, where you feed in the token (action) and transition (reducer) onto the next state.

Hopefully you can see that the new reducer feature is nothing but a thin layer on top of setState. The whole API is component-local, and doesn't disturb your existing state management solution. If you're really into crazier and potentially global state management solutions, the new API should still work under it. Stateless components haven't changed.

Head to the migration guide to see how you can upgrade your ReasonReact app. Surprise! It's a non-breaking change! What are you waiting for? =)

Below is a set of decisions we've made that justifies this API change.

Design Decisions

Why the reducer API? Upon careful examination, you'll realize it's an extra layer of indirection compared to the conventional state-setting: instead of invoking the callback directly through self.update, you now send an action through self.reduce, which is then matched inside reducer. We need to justify an indirection with its benefits.

For one, reducer is a first step toward easier ReactJS Fiber support. Fiber's concurrency requires us handling state updates & side-effects in a more principled way (more details another time). We've also decided to iterate on ReasonReact instead of core React, partially because we're Reason users and partially because ReasonReact's nature of being new & less used allow us to iterate exciting new APIs without legacy concerns and without churning existing ReactJS users (though discussions have already started on first-class ReasonReact integration inside ReactJS).

More technically speaking, the reducer centralizes all your state transitions in a single spot and provides, along with the action variant type you've defined, an overview of the whole behavior of a component (including principled side-effects in the reducer! See ReasonReact.SideEffects & ReasonReact.UpdateWithSideEffects). This also solves some obscure corners of past ReasonReact, while enabling potentially much better testing paradigms in the near future. Best of all: because it's just a thin declarative layer on top of ReactJS' imperative setState, reducer doesn't have many technical unknowns. If you're used to setState, you know reduce. When the language provides great variant facilities already, not much else is needed in order to compose features together into a great API.

The new ReasonReact is already live on messenger.com. Again, don't forget to see the migration guide and the corresponding migration script.

Enjoy!

Want to read more?
Back to Overview