đź’ľ The State of State
Mitch Clay
Posted on May 3, 2022
As mobile and web apps grow there is often the need for global state management to help store, persist and manage the state of the application. This article will dive into an overview of the contenders in the market, as of January 2021, and our roadmap at Chipper Cash to a predictable & scalable state management solution.
🤔 The Problem
The Chipper Cash mobile app has grown, very quickly, to support a number of different products and features which has unfortunately left our state management in the dust as it becomes more complex and harder to understand. In true start up fashion we’ve shipped fast to prove ideas and our business model. However as we scale from a small to large engineering team we needed to consider our current state system and patterns so that we can ensure a consistent, testable and bug free application.
When thinking about global data we can break it into two parts; data that comes from remote sources such as an API or by the user entering data locally for example via a form.
The goals? To improve maintainability, the ease at which the codebase can scale and network performance for our users.
👨‍🏫 History
Before starting this project the mobile app used MobX (version 4) for state management, we had a single store file including over 1500+ lines of code used to fetch, store and update both local and remote data. This store is then used within any React component that requires access to the data. We’ve seen a few favourite patterns emerge - but it’s pretty close to a free for all.
🤷‍♂️ Options
This section aims to cover the state of state management in React as of January 2021 - the goal is to provide insights into the research and reasoning for the commentary that follows.
Mobx
MobX itself is a "state management engine" and needs to be paired with patterns to allow the state to grow in a maintainable and predictable way - the thing we are currently missing. MobX helps automatically derive application state in a straightforward, un-opinionated manor using functional reactive programming concepts through an observable pattern.
As Mobx is a "state management engine", similar to Context, it is often paired with libraries such as MobX-State-Tree offering the structure and the common tools needed for applications.
Overall the latest version of Mobx is a good contender for state management however the reactivity approach, lack of solidified opinions and reports of poor scaling in larger applications made us hesitant.
Context
Context is a React API allowing engineers to avoid prop drilling and directly inject state into React components further down the component tree. On the surface it looks like Context would be a good contender however it's just a tool and not a state management solution, meaning as the app scales we would most likely end up building our own system for managing state - maybe even leaning on the principles set out in Flux.
- Context is often referenced as being more suited to infrequent updates like theme and authentication logic. See Dan’s Tweet and Log Rockets Pitfalls of Overusing Context for a deeper dive into this statement.
- At one point Redux used Context under the hood and then switched away due to performance issues as referenced in the release notes - Although this may now be resolved.
- Why React Context is Not a "State Management" Tool is a great read that echos the concerns above.
Overall Context isn’t really what we were looking for - it’s a tool. With this we felt we would end up re-inventing the wheel by designing our own state management solution and patterns on top of Context.
Redux
Redux brands itself as the predictable state container for Javascript apps, at its core its built on top of the Flux architecture. For this reason there's a clear way to store, structure and update state in a testable, immutable and repeatable way. However it's still un-opinionated on store setup, what your state contains, how you want to build your reducers and tackle async actions.
- As we dispatch actions to update our state in Redux we'll be able to easily keep track of what is happening and easily debug errors and replay state. - note: a similar patten is available with MobX-State-Tree.
- Redux is famous for the large amount of boilerplate needed to achieve simple tasks. In a small application this can be cumbersome and Mobx could be a more concise option, however as applications and teams grow this becomes less of an issue, especially considering the below point...
- The Redux team have recently built Redux Toolkit (RTK) to address the boilerplate concerns and provide an opinionated approach to structuring a Redux app. More info over at Redux Toolkits site.
- Redux has been around for a long time - at least for the Javascript world. I recommend reading a History of React-Redux for a more detailed overview of how Redux got to where it is today.
- Redux allows us to build complex flows and even tie these flows in with other actions. For example whenever a new
verificationStatus
for a user is returned we could trigger a Saga which would check a number of rules before routing the user. This can then be tested with a library like redux-saga-test-plan. - Redux does not support async flows by default. An engineer has the choice to use Thunks or Sagas. TLDR: Thunks = Simple, Sagas = Complex - but great power. The level of granularity with these solutions is low, allowing great flexibility and again the need to design our own best practices. Sagas and Thunks are different tools that can achieve both similar and different things; however consideration should be used on the most appropriate tool for the job.
- RTK is a great start to improving Redux, however the flexible async flows have left a hole for "what is the best practice for managing async code". This is where tools like React Query, discussed below, have started to fill the gaps. The Redux community have been addressing this with RTK-Query which is a "simple to use data fetching and caching library".
- RTK-Query provides a comparison table here with other solutions.
- Check out Mark's Dev Blog for some amazing content - specifically the Idiomatic Redux Series.
Example
We created a basic data fetching & counter example in Redux with RTK & RTK-Query. A partially nice feature is the auto generated and typed API created with @rtk-incubator/rtk-query-codegen-openapi
which is injected into Redux through middleware allowing us to simply call:
const {data, error, isLoading} = api.useGetChargesQuery({});
in our component with no extra boilerplate. - Very similar to React Query below but with the added power of Redux behind the scenes for more complex flows plus it would keep all our data in a single source of truth.
React Query
The previous libraries are all about storing and updating global state, the data mentioned may come from remote or local sources. React Query focuses exclusively on the remote part and provides React hooks to easily request and use external data in React components.
- The interface for React Query is super simple and nice. Even the new RTK-Query library is inspired by this.
- React Query would most likely also require one of the other solutions in the long term to help manage local state and larger app orchestration.
- React Query provides a comparison table here with other solutions.
Example
We created an example of React Query matching the Redux example above. The auto generated API in this example was more refined than the one we saw by RTK-Query but this is as expected from a more mature library.
Recoil - Honorable Mention
Recoil is an experimental set of utilities for state management in React by Facebook. Check out this video for more details - it's cool but early days for adoption. This does demonstrate how state management is still being improved upon & designed in the React ecosystem. Certainly one to watch!
🚀 Our Future
Considering all of the above, this section covers our future approach to state management. As stated, no pun intended, at the start there are are two key concepts to keep in mind both local user inputted data and remote data fetching/caching (APIs).
🍦 Contenders
We considered a few different flavours of the above...
- React Query & Redux + RTK. React Query to handle API Calls and Redux and RTK for global state management.
- Redux, RTK & RTK-Query (Query is now part of RTK). Meaning our Redux store would be the single source of truth for all data.
Opting for a Redux only solution felt like it was going to be the best path forward, it allows us to manage all application state in a single place with similar concepts and will avoid the need to potentially duplicate state across React Query & Redux.
Utilising Redux at its core and leaning on RTK for reduced boilerplate and best practices will mean we can get up and running quickly with a standardised scalable approach. Along with us picking a tried and tested solution that we know future developers would be excited to work with.
When approaching remote data with API calls, it's clear an interface and features that a library like React Query offers is the preferred method. For this reason opting for RTK-Query felt like a great place to start even with the risk of it being in alpha when we started. We could have looked to build our our own async flow patterns and helpers however using & supporting RTK-Query felt like a more sustainable long term solution.
⏱ Fast Forward
It’s been a year since we first wrote the internal specification that resulted in our migration away from Mobx to the solution outlined above and we’re still working on this project to date as we balance technical debt and pushing new exciting features to our users. So far we’re happy with our choice, over the year we’ve seen...
- Redux Query - evolve to be part of RTK and graduate into production.
- Users having improved network performance.
- Easier to maintain API with code generation and types for our Redux Query client - blog post coming soon on this one.
- The luxury of using hooks to access our API data.
- The splitting of redux slices per feature area meaning even our state management is scoped to avoid bugs occurring across feature domains.
- A much easier to maintain codebase and state.
- A codebase we feel confident scaling with a large number of new engineers.
📣 We’re hiring - check out the Chipper Cash careers page to see our currently open positions.
Posted on May 3, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.