JOIN Stories is making the transition from Redux to Recoil: how and why?
Anyone who has taken up the challenge of developing a creative tool knows that data management is one of the biggest technical challenges to face.
The React ecosystem has numerous data management libraries, even more so if we take into account the derivatives of these libraries. Chez JOIN Stories, we wanted to review our data management to take a more atomic approach, which motivated the migration from Redux to Recoil.
For the curious and for those who will gain knowledge for their own tools, we tell you our story.
The context
JOIN Stories, an innovative tool that allows you to create, broadcast and analyze Immersive and impactful Web Stories. Exactly what do we do to ensure a product that meets expectations?
The JOIN Stories interface allows you to create content in Web Story format in an intuitive and dynamic way, like Canva or Figma. When editing a story, we manipulate it in the format of a complex JS object, containing N pages and each of these pages containing N elements.
Experience has taught us that, in practice, it can be difficult to manipulate and display such a complex object without creating abusive re-renders, or without remembering too large objects. What can we do to overcome these problems?
These are the types of issues we were having with our Redux-based data management stack.
Here you can see a schematic of our current Redux data structure:
The path from Redux to Recoil
First step: review our data structure
It is in this context that we decided to completely change the way we manage data in our App. In order to limit the problems, we decided to completely separate our data into a multitude of elements rather than manipulating everything into a single large object.
So, the Story (the main object), would no longer contain pages, but a list of page IDs. The content of the pages would be stored in a separate dictionary. In the same way, pages would no longer contain items, but a list of item IDs.
The separation of the data into different structures allows the different components to subscribe only to the necessary data, and therefore to limit unwanted re-renders
Here is a schematic of our new data structure on Recoil :
Second step: review our library choice
It is possible to implement this architecture with different libraries, but it seemed obvious to us to use Recoil. Why? Recoil highlights this atomic vision of data management.
In addition, we had already used this library previously to solve performance problems in a more restricted context. So we were confident in its ability to alleviate our problems.
We are starting to set up
First, the Atoms
To set up this architecture on Recoil, we therefore had to create:
- 1 object (Atom)
This first atom contains the information from the Story.
- 2 dictionaries (atomFamily)
The first dictionary contains the information of the pages (remember that there are N pages in a story).
The second dictionary contains elements (remember that there are N elements per page).
Story Atom
AtomFamily page
AtomFamily element
In our case, we decided to Divide our application into three atoms for a Story. This allows us to work with data that is small enough to avoid subscribing to useless data, but comprehensive enough to be relevant to use.
We then validated this data structure by carrying out initial tests to verify that the number of re-renders was not too large.
Then the Selectors
Now that we have data separated into several structures, accessing complete and aggregated data can be complicated.
Knowing this, it was important for us to set up various selectors that would allow us to recover less raw data, which would be easier to handle in certain situations.
With these selectors you can subscribe only certain specific components to this data, and therefore limit the possibility of unwanted re-rendering.
We have therefore set up the following selectors to be able to directly manipulate hydrated Page or Story data directly:
PageSelector
This SelectorFamily allows you to set and get a page containing the elements directly.
StorySelector
This selector allows Get and Set the full story. By complete, we mean who contains the information in the Story in addition to the information about the pages and the items in those pages. This allows us not to have to change the data structure in the database, since it is very suitable for no-sql.
Among other things, this allows us to initialize the store by directly giving it the object of the complete story.
Data display
Then, we create 3 components to make the story, pages, and items. Each subscribing only to their own data.
The use of react memo makes it possible to avoid an element from being rendered if its parent is updated but that does not impact it. For example, if you add an element to a page, there is no need to re-render all the other elements on that page.
Conclusion
In reality, the final structure is much more complex, and it can be difficult to make the transition from an existing codebase.
At JOIN Stories, the transition was complex, but we were able to observe strong performance improvements. We have seen the time of processor tasks divided by 2 over a set of actions.
As a warning to those who would like to follow our example, we would like to remind you that the improvement in these performances is also due to the change in the structure of our data, and not simply migrating to Recoil. Recoil is just the tool that seemed to us to be the most suitable for carrying out this migration.
Such a restructuring could also be done via Redux, which in fact we have not completely abandoned, since it is still used to manage user data, an object that is less complex and less often mutated.
→ At the risk of being repeated, we add that Recoil does not necessarily replace Redux, we are still using Redux for our global application store, because the atomic approach to managing our elements seemed more appropriate, and the response time of Recoil vs Redux had real added value.
{{cta}}