React with its component-based approach has made the life of developers easy by managing separate state and logic for each component and the ability to re-use them when required. But what if you wanted to use a shared state between multiple components or maybe fetch data from an API once and make it available to all the components in your app? That’s when global state management libraries like Redux, MobX, etc., come into play.
The downfall of Redux:
With the release of React 16.3, the react community got to experience the new Context API which worked similar to Redux and allowed to manage state in multiple components using Context Object, Provider, and Consumer. However, the context API also came with a catch! Sebastian Markbage from the React team has mentioned that the new Context API was not built and optimized for high-frequency updates but rather for low-frequency updates like theme and user auth management inside your app. You can check out his comment here. Context API also had some limitations with code splitting and the ability to store indefinite values instead of a single value.
So, what’s next?
The Basics of Recoil:
Recoil mainly comprises of two things — Atoms and Selectors.
Atoms are units of state. They’re updateable and subscribable: when an atom is updated, each subscribed component is re-rendered with the new value. They can be created at runtime, too. Atoms can be used in place of React local component state. If the same atom is used from multiple components, all those components share their state.
In simpler terms, Atoms are units of state that components can subscribe to.
A selector is a pure function that accepts atoms or other selectors as input. When these upstream atoms or selectors are updated, the selector function will be re-evaluated. Components can subscribe to selectors just like atoms, and will then be re-rendered when the selectors change. Selectors can also be used to calculate derived data that is based on state.
In simpler terms, Selectors transform the atom state either synchronously or asynchronously.
You should probably check out this video from the recent React Europe live stream to get a better understanding of recoil.
The face-off: Recoil vs Redux
Let’s start by creating a demo application with create-react-app which increments the count on the click of a button.
Our application is going to consist of MainComponent.js which receives the count and handleFireClick function as props.
Configuring the store:
In redux, we start off by creating a basic store that acts like a global state for our application.
In recoil, there is no need to create a separate store. Wow! that’s awesome 🤩.
Creating a shared state:
In redux, we create the desired application state using reducers. Let’s create a simple counterReducer which increments the count by 1.
In recoil, creating a shared state is a straight forward approach using atoms. Each atom can be considered as a single shared piece of state. Now, let’s create an atom for holding our counter state.
Firing the actions:
In redux, actions are fired using the dispatch method provided by redux. Let’s create a file named actions.js which holds the action for incrementing the count.
In recoil, we fire off actions and modify the existing shared atom state using selectors.
Connect ’em all:
Finally, it’s time to connect the shared state with our MainComponent.js component and to differentiate Redux from Recoil, I’ll be creating two new wrapper components called ReduxExample.js and RecoilExample.js.
In ReduxExample.js, we use useSelector and useDispatch hooks provided by react-redux to get value from store and fire actions to update it.
Bonus: In ReduxExample.js we can also create a class component and wrap the component using connect HOC by redux and pass the shared state, actions as props using mapStateToProps, and mapDispatchToProps respectively.
In RecoilExample.js we can directly use the shared atom state value with useRecoilValue and update our state, as simple as doing a setState but with useRecoilState.
Note: Recoil is based on react hooks and will only work for functional components.
There is just one more step for your app to start working and that’s by wrapping your Example components with HOC components provided by Redux and Recoil.
For the Redux example, we use the Provider from react-redux and pass our ReduxExample.js component as children. Make sure to also supply the store you created in the first step.
Similarly, for Recoil, we wrap the RecoilExample.js component using the RecoilRoot component provided by recoil.
At this point, we have successfully created the shared state and actions to update it using both redux and recoil. Simply run npm run start and check if your code is working.
The main question — Will Recoil replace Redux?
That’s a tough one to answer today (at the time of writing this post) as it is still in the experimental phase but the new library looks promising and there is a high probability that developers will switch to Recoil from Redux shortly.
Why? Recoil lets you create a data-flow graph that flows from atoms (shared state) through selectors (pure functions) and down into your React components without having to deal with store creations and re-render the entire App component tree while the state updates.
So, this was a quick comparison between recoil and redux for a simple increment counter app. You can check the live version of the app here (Redux-vs-Recoil) and source code on Github. It’s just a matter of time Recoil becomes the new standard for managing shared states in your react app and is ready to be used in production applications. Until then give it a shot and follow the community to keep yourself updated with the new releases. If you found this post helpful in understanding the basics of redux-vs-recoil drop me a message in chat on — What features would you love to see in Recoil? I’d love to hear any thoughts on this topic 😋.