Atom-Based Architecture ⚛️
Jotai’s architecture is based on the concept of atoms, where we work with independent and reactive units of state. Each atom can represent a unique source of data, holding either a primitive value or a derived state from other atoms, allowing for fine control over the behavior of global state.
Atoms are created in an immutable way, where only an initial value or a function for derived values is defined, making dependency and update management much more efficient. Through this atom-based architecture, Jotai automatically optimizes renders for components that consume these states, avoiding unnecessary re-renders.
With useContext, as the project grows and shared state increases, any change in context can force a re-render of all consumer components, even if only a small part of the tree actually uses the updated data. This negatively impacts performance and increases code complexity. Jotai avoids this problem by allowing for granular and independent states through atoms. In the project I am currently working on, we migrated from useContext to Jotai and saw a significant performance gain, as well as eliminating phantom bugs caused by excessive re-renders.
Main Challenges in React State Management 🗯️
One of the biggest challenges in managing state in modern React applications is the increasing complexity of user interfaces and the need to efficiently share data between components. It is crucial to control UI updates to guarantee performance and avoid bugs caused by unnecessary re-renders.
Prop drilling is a common problem: it happens when data or functions are passed through many layers of components, even if some do not use them directly. This creates a chain of information passing that makes the code more complex and prone to errors.
To tackle these challenges, several libraries like Redux, MobX, Zustand, Jotai, and Recoil have emerged, each created for specific solutions and with their own complexities. Although React offers native features such as the Context API, it may present limitations, especially in larger and more scalable projects.
Practical Examples ✍️
There are several ways to implement atoms; here are three examples:
Primitive Atom (Read/Write)
In Jotai, it is possible to create a primitive atom for reading and writing, functioning similarly to useState, but globally and immutably. This pattern is ideal for simple states shared across components.
import { atom, useAtom } from 'jotai'
// Creating a primitive atom
const counterAtom = atom(0)
function Counter() {
const [counter, setCounter] = useAtom(counterAtom)
return (
<div>
<p>Value: {counter}</p>
<button onClick={() => setCounter(counter + 1)}>Increment</button>
</div>
)
}
Read-only Atom
This atom is ideal for derived states, calculations, and some projections that depend on other data, without needing to modify its value directly.
import { atom, useAtom } from 'jotai'
const counterAtom = atom(10)
const doubleAtom = atom((get) => get(counterAtom) * 2)
function DoubledValue() {
const [double] = useAtom(doubleAtom)
return <span>Double: {double}</span>
}
Write-only Atom
This atom is ideal for global actions, resets, or commands that modify the state without consuming its value.
import { atom, useSetAtom } from 'jotai'
const counterAtom = atom(5)
const resetAtom = atom(null, (get, set) => set(counterAtom, 0))
function ResetButton() {
const reset = useSetAtom(resetAtom)
return <button onClick={reset}>Reset Counter</button>
}
Read/Write Atom
This type of atom can be used for scenarios where the value is derived from another state and can be updated in a calculated way.
import { atom, useAtom } from 'jotai'
const counterAtom = atom(3)
const multipliedCounterAtom = atom(
(get) => get(counterAtom) * 3,
(get, set, newValue) => set(counterAtom, newValue / 3)
)
function Multiplier() {
const [multipliedValue, setMultiplied] = useAtom(multipliedCounterAtom)
return (
<div>
<span>Multiplied: {multipliedValue}</span>
<button onClick={() => setMultiplied(9)}>Set to 9</button>
</div>
)
}
Conclusion 🏁
Jotai stands out for its versatility and simplicity. It offers an efficient architecture for state management based on atoms, suitable for projects of all sizes. However, it is essential to evaluate each project’s specific needs before implementing. In the project I am currently working on, Jotai proved to be a great fit for both performance and efficiency, as well as for code organization and maintainability. The most important thing is to choose a solution that best balances performance, scalability, and ease of maintenance for the project’s context. If you’ve read this far, I hope you enjoyed it.
Top comments (2)
Fantastic clarity throughout—your breakdown of atoms and render optimization was easy to follow. The comparison with useContext and how Jotai avoids unnecessary re-renders really stood out.
Thanks Sofia! I'm glad you liked it, your feedback is very important to me.