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)
Diseña tu próxima gran imagen

Diseña tu próxima gran imagen

Ya sea para vender, promocionar o publicar, realza tu idea con un diseño que destaque.

Keep reading

Photoroom foundation diffusion model: why, how, and where do we go from there?
Benjamin Lefaudeux
Photoroom acquires GenerateBanners and launches Visual Ads Automation—first-to-market GenAI engine for large-scale ad creatives
Lyline Lim
Working smarter with AI-led qualitative analysis at Photoroom
Cori Widen
Building a fast cross-platform image renderer
Florian Denis
Photoroom partners with Genesis Cloud to lower carbon emissions
Lauren Sudworth
So you want to rent an NVIDIA H100 cluster? 2024 Consumer Guide
Eliot Andres
What's new in product: November 2023
Jeanette Sha
Explore new Gen AI features coming soon to the Photoroom API
Udo Kaja
Photoroom 2024 product recap
Jeanette Sha
4 times faster image segmentation with TRTorch
Matthieu Toulemont