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

reasonreact

#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.


#21

Am I wrong thinking that all these approaches suffer from the issue that, when any state branch updates, every component that is subscribed to the state gets updated? And not exclusively components subscribed to the updated branch? My current react-js app suffers from this right know, and I am wondering how a ReasonReact port might solve the issue.


#22

Haven’t tried the but I reckon the combination of immutable structures and structural sharing would make that fairly easy to handle? Fwiw statext handles that fairly well (in js React) and it should only be easier to do in Reason.


#23

Hi Ostera,

I’ve just posted questions on:
Discord > ReasonML > #react

…about how to separate application logic from ReasonReact, inspired by CycleJS’
main() > isolated side effects (vDOM).

Here is a snippet:
"I think there is a lot of merit to the way CycleJS is looking at app design and I’m hoping to take aspects of it and incorporate it into a real functional language like Reason as I learn to program. And with a pattern like Callbags it should be possible to implement declarative, funcitonal, reactive programming.

I guess what I’m really asking is are there any established approaches in ReasonReact that:

  1. keeps imperative programming to the absolute minimum?
  2. keeps side effects isolated from the main app logic?
  3. manages all application state, except for occassional state not shared by UI components, in an immutable singular store (like Redux or Onionify)?
  4. allow us to stream data into components rather than calling “state” properties and methods in an OO style as is often done in ReactJS?"

How can we define a model for separate application logic/framework that can become a first-class citizen in the ReasonML community, alongside the standard React way? One where we use elegant patterns rather than opaque libraries and APIs?

Why is the traditional method, of sprinkling application framework throughout React, still something that is embraced by the functional ReasonML community? Feels like a contradiction of why we are using a functional paradigm.


Should I go with Reason for my next production app?