Simpler state management for React apps: observable stores and Higher Order Components

Simpler state management for React apps: observable stores and Higher Order Components

Maintaining global state is already solved in React: Redux, Mobx or React Context already have paved their way as 'de facto' solutions for maintaining state in large React apps. But at the end of the day everybody has concerns about those three solutions being too much boilerplate, too much magic or high complexity for even the smallest cases.

As a React Native developer I worked in large projects using Redux and MobX and they simply work despite all the hurdles so why bother looking for other patterns of state management?. Well, the rise of GraphQL changed a lot of things in a very short period of time like moving part of our app state to the back-end. The truth is, Redux and GraphQL can work well together but the complexity of maintenance rises tremendously as both solutions have their own gotchas and they take time and pain to master. The relationship of GraphQL with MobX is a different animal: they can work together but the don't like each other's patterns resulting in a sea of "why is this component not re-rendering?".

In my last project we wanted to use GraphQL as a remote store but of course we couldn't save all our application state on it because some pieces of it had to be available offline, were not worth an HTTP call or simply didn't belonged any other place than the front-end. For example: wether a user is logged in or not.

Study case: isLoggedIn

If you are a developer with more than a month of experience you probably had to deal with the logged in/logged out state of an app. Let's say this is the only piece of state you need to maintain in the front-end because everything else can be stored in a GraphQL API. Wouldn't it be a hassle to have to install Redux or MobX and do all the configuration and boilerplate required just for one boolean?

That was the case for the app we were building so we started investigating ways of locally storing this boolean in the front-end but making sure all the components/containers needing this piece of information were properly notified when it changed.

The obvious solution was to use React's Context but as we tried it we felt it polluted our components tree with Providers, Consumers and other tags. It was an overkill for something as simple as updating some screens with a new prop (isLoggedIn) whenever we detect it changed.

The simplest solution

Injecting new props into Components is not a new pattern, Facebook has been recommended using Higher Order Components in React since they launched it. So, why not using it? The simplest solution is to have a HOC named withLoggedIn which will inject the prop isLoggedIn to any Container or Component interested in receiving this information.

That's it, problem solved. But wait, how would we update all the Components decorated with our withLoggedIn HOC? Redux uses dispatchers which will trigger actions which will be recieved in reducers which will calculate the new state. MobX uses the observer pattern, a very old design pattern in which a "subject" maintains and notifies a list of "observers" when significant changes occur to it.

Design patterns are proved solutions to common problems and the observer pattern is actually a simple and effective way of keeping all the components in sync with the data they need from the state so why not use it in our withLoggedIn HOC?

The idea is quite straightforward: we need to make our isLoggedIn variable observable so our withLoggedIn HOC can inject it to decorated components everytime it changes. The first question to arise is, how do we make isLoggedIn observable? Well, we could build our own PubSub module by keeping a list of subscribers and a mechansim to notify them whenever the subject changed but there are already a bunch of lean and fully featured solutions for reactive programming, RxJS being the most popular one.

Adding RxJS to the mix

MobX and RxJS are both inspired on the Observer Pattern but they are very different in nature. While MobX is an opinionated library, RxJS offers developers a bunch of tools to incorporate Reactive principles into their projects. It's up to those devs the way the want to use and how deep they want to go through the rabbit hole. That makes is a great tool for this article as simplicity is key. Let's take a look at how we can build an observable store for our isLoggedIn variable (we will make it generic so we can store more user data on a later stage).

javascript
import { BehaviorSubject } from "rxjs"; class User { observable = new BehaviorSubject(false); updateIsLoggedIn = (isLoggedIn) => { this.observable.next(isLoggedIn); }; } const user = new User(); export default user;

Here we just created a simple store named user which can be updated by other components by calling user.updateIsLoggedIn whenever the user has logged in or out. The reactive part of the store is defined by an object named observable which will be provided by RxJS as a BehaviorSubject (which receives the initial value for the observable item). RxJS makes sure all observers are swiftly notified whenever this.observable.next(isLoggedIn) is called.

That's it, only two lines of code are enough to make our store reactive. Now we need to build our HOC to connect this reactive store to any container or component interested in the value of isLoggedIn

Creating our HOC

Higher Order Components are the React version of the decorator pattern, a well established way of adding extra functionality to components. As any popular design pattern, HOCs have a pre-defined structure: functions which will return the same component but with some extra props being injected into it. In our case we will subsribe the HOC to changes in the store (the observable object) and then update the HOC's local state forcing a re-render of the wrapped component whenever the observable changes. This is easier to understand by taking a look at the following example:

javascript
import React from "react"; import user from "src/stores/user"; const withLoggedIn = WrappedComponent => { class withLoggedIn extends React.Component { loginSubscription = null; state = { isLoggedIn: null }; componentDidMount = () => { this.loginSubscription = user.observable.subscribe(isLoggedIn => { this.setState(isLoggedIn); }); }; componentWillUnmount = () => { this.loginSubscription.unsubscribe(); }; render() { return ( <WrappedComponent {...this.props} isLoggedIn={this.state.isLoggedIn} /> ); } } return withLoggedIn; }; export default withLoggedIn;

As we expected, the HOC takes a component and uses React Component's state to store and re-render the wrapped component on every change of isLoggedIn.

Using it all on our components

We are done setting up so now we can use the store and the HOC in our React Components tree to make any parts of our app aware of the isLoggedIn state. Let's say for example we have a HomeScreen displaying different content depending on wether the user is logged in or not; we could do something like this:

javascript
class HomeScreen extends React.Component { renderLoggedInVersion = () => { return ... } renderNonLoggedInVersion = () => { return ... } render() { const {isLoggedIn} = this.props; return ( <Header/> {isLoggedIn && this.renderLoggedInVersion()} {!isLoggedIn && this.renderNonLoggedInVersion()} <Footer/> ); } } export default withLoggedIn(HomeScreen);

We simply applied our HOC withLoggedIn to the HomeScreen so it receives the change notifications for the general state variable isLoggedIn as a prop.

The last piece of the puzzle is how and when to update the isLoggedIn store and this is done by just calling the update method we created for such a store previously.

javascript
import user from "src/stores/user"; class LoginScreen extends React.Component { onLoggedIn = () => { ... if(success) { user.updateIsLoggedIn(true) } ... } render() { const {isLoggedIn} = this.props; return ( <Header/> <LoginButton onPress={this.onLoggedIn}/> <Footer/> ); } } export default withLoggedIn(HomeScreen);

Done! When the login is successful on the LoginScreen, it updates the isLoggedIn store and, thanks to RxJS, it will update all the components decorated with our HOC. We can use withLoggedIn in all screens/components we want because RxJS ensures the notifications will be delivered on time and in a very performant way.

Is this a MobX killer?

No. Definitely not. This is a simpler way of achieving the same results but it lacks tons of features so it's maybe more suitable for projects in which, as mentioned before, we already have GraphQL storing parts of our state but we want to add other parts which need not to be synched in the back-end.

This solution works, doesn't need much boilerplate, it's easy to reason about and creates a small footprint in the codebase but of course, dealing much more complex scenarios in this way may end up creating a larger mess than adding a library like MobX or Redux.

I try not to abuse this pattern in my apps as it's very tempting due to the simplicity of its nature mainly because in most of the cases, hving two separate sources of truth (stores) can be confusing and difficult to debug.

Read similar articles