Back

Refactoring a live mobile app with 1000s of active users

Recently at Despark we introduced a big architectural change to one of the apps we are actively working on and running in production. We’d adapted a financial web application to mobile, building native apps for both iOS and Android using React Native. With time and growth, the platform demanded an upgrade, and the evolution of our own tech stack meant that now was a good opportunity to take the plunge and refactor its core architecture using Jotai.

This is a story about a specific solution for some problems we were facing. We look at how it benefited the product, helping our team move faster with more confidence and make new features easier to implement. We love trying out new technologies to solve specific needs, yet balancing this with the stability of a running application and upcoming new features can be challenging. And now we are about to get technical.

Our state management journey

We have been using React for a wide variety of projects. Whether it was building demanding interactive web interfaces, performant mobile applications with React Native, quickly scaffolding internal tools, or building a product MVP with a quick time-to-market -- React has worked very well for us. But our state management approach has varied throughout the years. We started using Redux shortly after it was introduced in 2015 and it emerged as a popular library utilising the unidirectional data flow similar to the then popular Flux architecture. It allowed us to have a sensible architecture both for web projects like Kooth, and rapidly developing React Native apps like HealthyCo. It is still a very popular, widely used and stable library; it's so good that its original creators are now part of the React core team!

Later on, we introduced GraphQL to our tech stack and we noticed that a lot of the state we were keeping in Redux was related to data we were receiving over the network, whether it was the data itself, loading flags, pagination information, etc. Apollo, the client which we were using to communicate with our GraphQL APIs, handled this so well that for the majority of the use cases we didn't really need to store a lot of state. We slowly moved away from Redux. The current context API was introduced in React 16.3 at the beginning of 2018 and the combination of Apollo + Context worked very well for us in projects like Future Steps, Altrix, Tamedocs, and recently UNICEF.

Along the way, we also experimented with XState, MobX and a few other state management solutions, but we had a different problem at hand here.

Lots of moving parts, several possible solutions

Because the backend was already in use by a different app, we couldn't use GraphQL for this project, which meant that we had to keep more state on the client. `react-query` works well for handling network data and its metadata, but the pieces of the app were becoming more interconnected than we initially anticipated, real-time connections demanded more control and because the API was used by multiple clients some things were harder to change. After a while, we ended up with a classic scenario of "context waterfall". Some performance problems were starting to appear and generally relying solely on `react-query` and Context felt like going against the grain at times. We needed to change our approach.

Avoiding Context waterfalls by using JotaiWe explored several modern state management libraries. Dave McCabe's Recoil talk at React Europe 2020 gave a really good overview of Recoil, Facebook's minimal state management library utilising an atomic approach. We also considered Zustand by the Poimandres collective: we previously used other libraries by them and really liked the experience of `react-spring` for animations and `react-three-fiber` for 3D, as well as their approach to building and maintaining popular open-source libraries.

For our use case, we chose Jotai, the primitive and flexible state management library from Poimandres, developed mainly by Daishi Kato. It felt really "react-like", with a more minimalistic approach than Recoil, yet keeping the same atomic way of working. We needed a replacement for `useState`+`useContext` and as the comparison page mentions -- Jotai does fit well indeed.

What worked for us

We knew that we had to incrementally refactor away from our use of contexts, and Jotai's approach made it easy for us. It allowed us to remove our context waterfalls. It made it easier to share data across our components. Here's all that we needed for sharing a boolean atom value in multiple places, as opposed to tens of lines in our previous context implementation

import { atom, useAtom } from 'jotai'
const isAlertOptionsOpen = atom(false)
export const useAlertOptionsIsOpen = () => useAtom(isAlertOptionsOpen)

 

This felt really lightweight, ergonomic and "react-like". Previously every time we had to create a Context just to share a single value it felt like too much work. Creating derived atoms is also a breeze, same as async atoms and their integration with Suspense.

const resetAuthAtom = atom<null, undefined>(null, (_, set) => {
  set(tokenAtom, NoToken.NotLoggedIn)
  set(currentUserAtom, initialUser)
  set(recentlyViewedListIdAtom, undefined)
})

 

There are features which take some getting used to, such as `atomFamily` which we have used for specific lists of data, dynamic `selectAtom`, and write-only derived atoms, which actually solved a problem we had quite elegantly. But those features are powerful, and once you play around with them they become second nature. For the majority of the cases, we were using Context to share “global” data across the component tree, and this was really easy to refactor.

Looking to the future

Would we choose Jotai for our next project? The answer, as it often is with many technological choices, is - it depends. While it is not nearly as popular as the more established solutions, it worked very well for us in this particular scenario.

If you are just trying to solve performance problems with Context, there are different ways to approach this. Daishi Kato, the creator of Jotai, Zustand and other state management libraries has an article outlining several approaches. Furthermore, there might be several different solutions coming to React itself -- from `useContextSelector` in the shorter term, to something like "auto memoizations" inserted by the compiler, as mentioned by Andrew Clark in the Context Selectors Experiment.

And while more thoughtful context structure, as well as manual performance improvements could have greatly remedied our initial problem -- we simply found it delightful to work with Jotai, and we refactored the whole thing in just a few days. It taught us to remain open to exploring new solutions, balancing this with a demanding roadmap, and learning a few things along the way.

--
If you found this technical overview valuable, share it with your colleagues, and subscribe to our mailing list to stay updated. Like the sound of our Despark frontend team? Maybe there’s a seat with your name on: hop over and check out our current vacancies.

Written by

Rossen K.

Frontend/React Developer and Team Lead at Despark