What's the best way to handle global state in ReasonReact?

reasonreact

#1

As the application grows larger, using the components state and passing it down as props becomes very hard to handle.

There’s also a way of extracting the state like described in here: https://medium.com/@Hehk/creating-global-state-in-reasonreact-f84701c6ab6

but as the author of the blog post said, it may quickly become very boilerplate’y. Are there any other solutions?


#2

I have not tried it yet in ReasonReact, but apollo-link-state looks like a promising solution for managing global state, especially if you’re already using apollo/graphql.


#3

I’m looking more into pure ReasonReact solution I think.


#4

I don’t think there is a pure ReasonReact solution yet, maybe the new React context API will change that.


#5

Have you looked at https://redex.github.io/package/reductive ?


#6

reductive is a reference implementation and you should be careful when using it

There is also https://github.com/Hehk/reason-react-context which implementing Context v2 in pure Reason. It should be easy to switch to the “real” context v2 once it’s out


#7

I’d recommend it. It’s maintained by @rickyvetter :wink:


#8

^ Reductive is indeed a reference implementation; we try to keep it polished (as always), but as per the README, you might not need it. Relevant: https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367


#9

@chenglou how are you managing the state at messenger.com?


#10

messenger.com persisted state is accessed as if it is component state that is passed to child as a render prop. Think something like:

<MessengerState>
    ...(messengerState => <MyMessengerApp messengerState />)
</MessengerState>

The actual implementation of MessengerState is being iterated on. We are looking at options where the component is just a shell to forward state from some external store and we are also looking at options where the component itself is the store and it uses React as the sole subscription/update provider.

Regardless of the implementation, the consumer api is always the same - which is the boilerplate-y concept you’re concerned about. We haven’t moved all of our state to Reason yet, but right now I can say that this boilerplate doesn’t feel hard to work with. We use prop punning pretty heavily which makes it a bit nicer and the explicit tracking of data can be very nice for understanding an application and debugging.


#11

Can you give me a hint about the implementation of the external store?


#12

a hint about the implementation of the external store

@shinzui - it rhymes with mobile mingleton (at least with my American accent). If you’re interested in the specifics of the implementation I’d recommend playing around with the dev tools in your favorite browser. It takes a bit of effort, but can give you much more specific (and potentially interesting!) insights then I’m able to.


#13

Here is something that I came out with, interested in what you think about it:

Store.re

type action =
  | ChangeWorld;
type state = {text: string};
let initialState = () => {text: "world"};
let reducer = (action, state) =>
  switch action {
  | ChangeWorld =>
    ReasonReact.Update(
      state.text === "world" ? {text: "hello"} : {text: "world"}
    )
  };
let component = ReasonReact.reducerComponent("Store");
let make = children => {
  ...component,
  initialState,
  reducer,
  render: self => children(self)
};

and usage:

Home.re

let component = ReasonReact.statelessComponent("Home");

let make = _children => {
  ...component,
  render: _self =>
    <div className=Style.container>
      <Store>
        ...(
             ({state, send}) =>
               <div>
                 <h1> (ReasonReact.stringToElement(state.text)) </h1>
                 <button onClick=(_e => send(Store.ChangeWorld))>
                   (ReasonReact.stringToElement("change"))
                 </button>
               </div>
           )
      </Store>
    </div>
};

#14

It looks like you treat Store as singleton when it isn’t. You instantiate new Store every time you render <Store /> so I don’t think it would work?

Currently, I have my root container implemented in JS which uses current context API. I postponed its conversion to Reason until new context API is implemented. And meanwhile, I just created context provider in JS which reads data from container and passes it to Reason components via FFI.


#15

I’d argue that an approach for handling big, possibly opaque data structures that has been adopted by the Haskell community is using lenses.

We’ve been using the Ramda implementation (http://ramdajs.com/docs/#lensPath) of them in a pretty complex global state for a Javascript app.

But then you enter the world of having your application itself outside of react, and only using react for the DOM/Events. Some people will feel icky there.


#16

I’d actually written about this around Feb. Thought I’d post this here since this post has been bumped up.

Medium: Application State in ReasonReact


#17

My take on it (created for a similar question on Discord): https://gist.github.com/jsiebern/ac9a3ce507ad047f7c1d594446cd7b4e


#18

I’d strongly refrain from encouraging people to use React as the application framework.

We’ve already seen how impossibly complicated things get down the main layer of abstraction with libraries like react-redux, react-redux-form, react-router, and many other examples.

Keep your components for views, handle your data outside of it. There’s little to be lost in doing

let render = (~domId, ~component, state, dispatch) => 
  component(state, dispatch)
    |> ReasonReact.element
    |> e => ReactDOMRe.renderToElementWithId(e, domId)

Store.subscribe (render ~domId="root" ~component=App)

And suddenly there’s no magic component doing any gluing, just a rendering side-effect ocurring on state changes.


#19

It’s just an implementation detail as @rickyvetter said - I can’t see any harm in using a component to provide the state (esp. in reason as the state management is very well implemented), when the actual result of using the state / dispatch logic down the tree is the same. Having a Store component at the top of the tree is, to me at least, a lot more readable than a subscribe function. Wether the store component uses the internal state management or you abstract it further into a solution like you proposed - that doesn’t make a difference then anyway.

In the end, both are perfectly valid concepts (and probably a lot more as well). It’s a matter of preference.


#20

Pretty similar to your idea @knowbody, I made this thing in React that I want to port to Reason when I have time: https://github.com/eldh/statext/

It would basically be a ReducerComponent that shares its state between all instances.