Inside PhotoroomStories from the team

Picking a state management library for a React app used by millions (and why we went with MobX)

Photoroom is an image editor used by tens of millions of users around the world. As our web app team was crossing 5-developers mark, we found ourselves running into more and more of the same issues: the whole app was rendering too often and we were frequently dealing with an endless list of useEffect dependencies.

What’s more, we were paving the way to introduce real-time editing: this implies a reliable shared state across iOS, Android, and the web.

We needed to rationalize state management in the app and migrate from the extensive use of useContext (with Constate). This article covers why we picked MobX, the transition and why we’re not looking back.

Zustand vs MobX vs Redux

When it came time to take a decision that was probably going to impact the life of our app for the next few years, we used the tried and tested “measure twice, cut once” strategy

Members of the team pushed for Zustand but while we loved its simplicity, it also meant that it was less opinionated. We also considered the good ol’ Redux. The name triggered PTSD from copying boilerplate state code around 10 years ago (although I’ve been told the Redux Toolkit make it much less verbose these days)

What seduced us with MobX was its philosophy: anything that can be derived from the application state, should be derived. Automatically. Which is great when you’re creating a photo-editing app with sliders, UI boxes, a lot of server calls to AI models. MobX works by setting native JavaScript Proxies on objects. If an object gets updated, it’ll know about it and only re-render your component if necessary.

The transition

As a proof of concept, we started by using MobX in the most complex part of the app: the content sync (syncing user designs with the server). A lot of the logic around the state was already pure TypeScript, making it a good candidate. Converting this part of the codebase was painless (shout-out to Jimmy!), convincing us that MobX was a great match for our needs.

We then proclaimed a 'no new state shall be created without MobX' law inside the web team, ensuring no new feature was to be built with useContext. We created a little Quick start and footguns to avoid document to share our learnings and we had a few workshops to learn how to use our new state library.

The most common mistake is to forget to wrap your component / subcomponent / sub-sub-component in MobX’s observer helper. If you don't... nothing happens when the state changes. About 13 <p> state debug {store.myVariable} </p> and a git reset later, you’ve learned your lesson and are now an official MobXer 🎉.

We then migrated existing state from useContext to MobX. It went smoothly but it required someone very thorough to ensure we were not missing any edge case (shout-out to Jonathan!).

The only road bump was the inability to use the useQuery hook from the amazing TanStack Query. Indeed, since your state is basically a class that does not live in the React world, you can’t use hooks. There are a few packages available, but we went with our own wrapper. Here’s how you use it inside a MobX Store:

Example of checking if a new version of the app is available inside a MobX store

The same issue occurred with other libraries like Tanstack Router or the React i18next, but thankfully they offer interfaces to query their state outside of React

What we gained from the migration

Now that the migration is almost complete, we’re clearly seeing the difference compared to the previous state of things (pun intended).

First, testing became much easier. With MobX, your state is basically a class with properties and methods. This makes testing incredibly easy, especially as you don’t need a DOM around to run your tests.

Second, developers are much more comfortable updating the state without having to grasp the re-render chain a one-line change will trigger. No more endless fiddling with the React developer tools to hunt for whole-page re-renders. No more circular useEffect dependencies.

Finally, the state is updated in a few select methods and we can enforce it. With a useContext state, any developer can decide to update the state in any location. Here's how we do it:

I’ll follow up soon with a blog post on how we to integrate our cross-platform internal SDK with the existing MobX store.

The downsides

Finding developers with extensive MobX experience has proven harder than anticipated. Although MobX is about 10 years old, finding people who (1) worked with it, (2) on apps with complex state and (3) are based in Europe proved more complex than anticipated.

I’ve been told that the best way for that was to write a praising blog post. So here we are: if you’re matching all 3 criteria: please apply here. We’re nice people, work in English and we have amazing challenges to tackle. Add the word “banana” to your application so we know you’ve been patient enough to read until the end of this blog post.

Eliot AndresCo-founder & CTO @ Photoroom
Picking a state management library for a React app used by millions (and why we went with MobX)
Conçois ta prochaine image de prestige

Conçois ta prochaine image de prestige

Que tu souhaites vendre, promouvoir ou publier quelque chose, donne vie à ton idée grâce à un design qui se démarque.

Keep reading

Building a fast cross-platform image renderer
Florian Denis
Photoroom foundation diffusion model: why, how, and where do we go from there?
Benjamin Lefaudeux
What's new in product: September 2024
Jeanette Sha
Photoroom’s approach to responsible AI
Lyline Lim
10 tools used to ship an iOS app in 2 weeks
Matthieu Rouif
So you want to rent an NVIDIA H100 cluster? 2024 Consumer Guide
Eliot Andres
Core ML performance benchmark iPhone 14 (2022)
Florian Denis
How we measured the CO2 emissions of our AI models at inference time
Matthieu Toulemont
We’re training a text-to-image model from scratch and open-sourcing it
Jon Almazán
Businesses need more threesomes, reveals market report
Aisha Owolabi