A Deep Dive into Atomic State Management
State management is one of the most important cogs of a React application. Developers can opt to work with built-in solutions, such as useContext or state management libraries like Jotai or Redux. This decision has a significant impact on the application’s performance and maintainability.
I decided to look into Jotai, a modern atomic state management library. Let’s take a look at React’s built-in alternative to state management libraries first.
React’s built-in alternative to state management libraries
useContext is a feature that has been available since React version 16.8. It aims to tackle the issue of prop drilling in React because one has to pass data through several components, where it might not be even required.
Similar to state management libraries, we define a context that contains the data we need globally and wrap the components inside its provider. As easy as it sounds, it does have its limitations:
Pitfalls of useContext
- Unnecessary re-renders: Let’s say the context contains an object
{name:'John Doe', company:'Google'}.
If we have a child component using only the name from this context will have to re-render even if there is a change in the company key, which it is not even using. - Slow Performance: useContext applications experience 75ms average update times for small changes, which is significantly slower than state management libraries.
Where it should be used
- For storing global data that does not need to be updated frequently, or static data. Example: theming data, authentication data, etc.
What’s the industry standard, and is there even a need for a new state management system?
Redux is the most popular state management pattern. It is a defined architecture that has a defined flow for any changes to be made to the state, i.e., the use of actions and reducers. This defined pattern becomes one of its key strengths as well as some of its downsides.
Advantages of using Redux
- The defined pattern prevents accidental state mutations.
- One of its biggest strengths is the Redux DevTools; this tool makes the debugging experience much simpler for developers.
- Since it’s the most popular library, there is abundant community support.
- Excellent performance. (65ms avg. update time)
Disadvantages of using Redux
- A steep learning curve.
- Extensive boilerplate requirements for even the simplest task, which slows down development.
- 15KB bundle size.
- Overhead for simple applications is excessive.
When to use Redux
- It should be used in large-scale projects like e-commerce platforms and enterprise dashboards.
- When the number of developers working on a project is high.
- When a project needs comprehensive monitoring and logging.
Jotai: Modern Atomic State Management
Jotai shifts away from the monolithic architecture and promotes a shift toward atomic state management. Instead of one big store, there are individual atoms which can be created, consumed, and updated separately. It removes the complexity that is usually associated with the traditional state management tools.
I created a repository in which I explored how easy it actually is to work with Jotai. Let me show a few code snippets to demonstrate that. Here’s the link to my test repository if needed.
- Add a provider
import { Provider } from 'jotai'
export default function App({ Component, pageProps }) {
return (
<provider>
<Component {...pageProps} />
</provider>
)
- Create some atoms. In my demonstration, I have created a single file with multiple atoms. You are free to create atoms in different folders and files. This is how simple it is to create an atom and start working with it.
import { atom } from "jotai";
import { atomWithReducer, loadable } from "jotai/utils";
export const counterAtom = atom(0);
export const DoubleCounterAtom = atom((get) => {
return get(counterAtom) * 2;
});
const userApiAtom = atom(async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
return res.json();
});
export const loadableUserAtom = loadable(userApiAtom);
export const counterReducerAtom = atomWithReducer(0, (value, action) => {
console.log(value, action);
if (action === "increment") {
return value + 1;
}
if (action === "decrement") {
return value - 1;
}
throw new Error("Invalid action type");
});
- Consuming and mutating the atoms, here I have created a simple counter using atoms. It is used identically to a useState, so there is almost no learning curve. It also has the standard action reducer pattern if required for code standards.
import { useAtom } from "jotai";
import { counterAtom, counterReducerAtom } from "../atoms";
import { useReducerAtom } from "jotai/utils";
const CounterButtons = () => {
const [counter, setCounter] = useAtom(counterAtom);
const [count, dispatch] = useAtom(counterReducerAtom);
return (
<div>
<button onClick={() => setCounter((count) => count - 1)}>Decrease</button>
<button onClick={() => setCounter((count) => count + 1)}>Increase</button>
<button onClick={() => dispatch("increment")}>IncreaseReducer</button>
<button onClick={() => dispatch("decrement")}>DecreaseReducer</button>
</div>
);
};
export default CounterButtons;
- Here is another example of using it with an Api
import React from 'react'
import { loadableUserAtom } from '../atoms'
import { useAtom } from 'jotai';
const DisplayUser = () => {
const [user] = useAtom(loadableUserAtom);
if(user.state === 'loading'|| user.state === 'hasError') {
return <div>Loading...</div>
}
return (
<div>
<p>User</p>
<p>{user.data.name}</p>
<p>{user.data.email}</p>
</div>
)
}
export default DisplayUser
Advantages of Jotai
- Atoms are automatically garbage collected, which prevents memory leak issues.
- Atoms can be combined and derived without the use of excessive complex logics, which makes it easier to manage a growing repository.
- Excellent Performance!! (25ms avg. update time), useContext took 75ms and Redux took 65ms.
- A very small bundle size of just 4KB! Redux was 15KB in size.
- Atomic Structure ensures performance even with increased complexity.
Limitations of Jotai
- It has limited debugging tools. It has all the basic tools, but it can not compete with the testing tools like ReduxDevtools.
- It is not as popular as Redux, so there is much less community support.
- Not adopted on a larger scale due to it being a new entry.
When should we use Jotai?
- It should be used in projects where data changes frequently.
- If performance is a priority, Jotai is a better choice.
- Medium-sized applications with complex component interactions.
- It should be used in projects where there are rapid development sprints and boilerplate code becomes a limitation.
Ultimately, the ‘best’ state management library is the one that best suits your project’s needs. However, I found Jotai to be particularly developer-friendly and easy for newcomers to adopt. It was a refreshing experience to start a project without the significant boilerplate often required by other libraries.