Kinga Hunyadi, Software Engineer at METRO.digital talks about React State Management with Recoil in a very friendly and visual tech article. Enjoy her valuable insights and keep an eye on METRO.digital, they’ll join us as Product Management Stage Partner at Devtalks Reimagined 2021.
Recoil is a state management library for React. It is still in experimental phase, but it looks really promising. The best thing about Recoil is that it works and thinks like React.
The most important concepts of Recoil are atoms and selectors. Atoms are units of state, while selectors are pure functions that calculate derived data from state. Selectors accept both atoms and other selectors as input. Components can subscribe to selectors or atoms, and will be re-rendered when the selectors or atoms change.
I will explain how Recoil can manage your application’s state through some examples. No, it won’t be another todo app. Our app will show a list of songs, and for each song we can get some extra info. I will share the GitHub repository at the end of the article.
First of all, we need to create a new React app:
Check that your app works on localhost:3000, you should see a page like this:
Then we need to add Recoil to our app:
We need to wrap our components that use Recoil in RecoilRoot. We can replace the content of App.js with:
Our app should still work and show the changes we made:
We will create a real-world-like example, so we will start with our client:
Now that we have our client functions, we can implement the atoms and selectors that will manage our app’s state. Each atom and selector will have a unique id. We will start with loading the song list. As our client function returns a promise, the selector’s get function will be async:
Next step is to create a component which renders the list of songs. We need to connect our component to the selector we just created. Recoil has some useful hooks for this:
-> useRecoilState — returns the value of the given state and the setter function for updating the value of given the state;
-> useRecoilValue — returns the value of the given state;
-> useSetRecoilState — returns the setter function for updating the value of given the state.
We will create the Songs component:
We should note that our selector is async, but React render functions are synchronous. Here comes in React Suspense, which handles pending data. We could also handle pending state with Recoil’s Loadable, or implement a handler from scratch, but we will use Suspense now:
Now in our browser we should see the list of songs:
That was easy, right? Now let’s see how can we get the details of a song. When we select a song, we want to see its details, like the artist and the year of release. We need to remember the current song ID. The ID is just a simple value, it will not be computed, so we will create an atom for this, instead of a selector:
Based on the current song ID we want to get the song details. We need another selector which calls the client function with the current song ID. Selectors can read other atoms and selectors using the get argument of the get function. I know it sounds a little confusing, but the next example will make it more clear:
We will now create the CurrentSong component, which renders the details of the selected song:
Then we can add it to our Songs component. The currentSongIDState atom can be updated from the component by using the setter function returned by useRecoilState. (Note that I didn’t want to add it to the App component, because I don’t want to show the “Loading…” state when nothing is selected. Of course, we could structure our app better, but for now it’s just fine):
If we click on a song we should see the details below the song list:
It was easy and fun so far, while working with read-only data, but in real-world apps we want our app’s state to get updated after doing an update on the server. For example, we might want to add new songs to our list. Here it becomes a little more complicated.
If you are used to work with other state management libraries, like Redux, then you know that the “global” state can be updated after updating the data on the server. Recoil does not have a “global” state, like other state management libraries, but coupled to RecoilRoot. That means the state can not be updated outside of the components/hooks.
But there is still hope… with Recoil we can achieve this by subscribing to server updates from useEffect, and updating the state from there. I know this is not ideal, but this API is still under development, and Recoil might handle this out-of-the-box. In conclusion, comparing it with other state management libraries (like Redux), it seems more “React-like” and simpler and easier to learn, so it might be a good alternative in the future.
You can find the GitHub repository here.