Sup people! How are you doing? Are you ok?
Is a pleasure to see you again!
This is the third part of articles of react management, wow!
I hope you guys liked the previous article where I talk about state management using the Redux with redux tooltik. Now we will create the same application, but using Jotai 👻. We are going to create a simple application but we will be able to see the code of the technologies and compare the implementation.
The meaning of the Jotai in japanese is... guess?
Jotai 状態 means State
I'll cover these technologies in each article:
Summary:
Introduction
According to the documentation of Jotai:
Jotai takes an atomic approach to global React state management with a model inspired by Recoil.
Build state by combining atoms and renders are automatically optimized based on atom dependency. This solves the extra re-render issue of React context and eliminates the need for memoization.
It scales from a simple useState replacement to an enterprise TypeScript application with complex requirements. Plus there are plenty of utilities and integrations to help you along the way!
Sooo yeah! Jotai is inspired on Recoil, for coincidence (or not) will be the part 4. But let's see the similarities of this libraries in the next part.
Jotai offers a minimal API to we manage our state in application, with many utilities and integrations. This library is so simple that I don't have so much say, so let's implement and I will cover some concepts beyond we code.
Just to remember again, if you did not saw the first part, I created all components that we will use in this and next parts on the first part. Soooo if you did not saw yet, I recommend to see on: State Management on React [Part 1] - Context API.
We will use the first version of the components that we created without the Context API integration.
Now with all concepts at hands, LET'S TO THE CODE!
Show me the code
Let's add the dependencies that we will use:
yarn add jotai
Wait, what? just that? 🤔
yup! we only need the jotai dependency.
Let's start adding the Provider of jotai on our main three of components. I just renamed the Provider as you can see on import, just not to confuse. But you can import just as Provider
from jotai
main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { Provider as JotaiProvider } from "jotai";
import { ButtonChangeTheme } from "./components/ButtonChangeTheme/index.tsx";
import { Content } from "./components/Content/index.tsx";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<JotaiProvider>
<ButtonChangeTheme label="Change Theme" />
<Content text="Hello World!" />
<App />
</JotaiProvider>
</React.StrictMode>
);
Now we can use the hooks that the Jotai offers to us...
I will create a directory on /src
called atoms
and for each feature I will create a folder with the name of the feature. So, we have two features in our app: change theme color (dark mode) and a todo list:
I will start with the dark mode feature. We will create a file on theme/store.ts
and create this code:
import { atom } from "jotai";
export const isDark = atom(false);
export const changeTheme = atom(null, (get, set) => {
set(isDark, !get(isDark));
});
AND THATS'S JUST IT!
But before we create the todo, let's talk about the concept of atoms.
Atoms we can think as a "piece" of state. It is very similar to useState.
According of the documentation:
The atom function is to create an atom config. We call it "atom config" as it's just a definition and it doesn't yet hold a value. We may also call it just "atom" if the context is clear.
An atom config is an immutable object. The atom config object doesn't hold a value. The atom value exists in a store.
Now let's see how the todo store will it be.
Let's create on atoms/todo
two files. First the types types.ts
(my preference) and the store.ts
.
The types.ts
contains just the type of our todo:
export interface Todo {
id: number;
label: string;
done: boolean;
}
I will create a file on store.ts
and the code will be:
import { atom } from "jotai";
import { Todo } from "./types";
export const removeTodo = (todos: Todo[], id: number): Todo[] =>
todos.filter((todo) => todo.id !== id);
export const addTodo = (todos: Todo[], text: string): Todo[] => [
...todos,
{
id: Math.max(0, Math.max(...todos.map(({ id }) => id))) + 1,
text,
done: false,
},
];
//Jotai
export const newTodoAtom = atom("");
export const todos = atom<Todo[]>([]);
export const addTodoAtom = atom(null, (get, set) => {
set(todos, addTodo(get(todos), get(newTodoAtom)));
set(newTodoAtom, "");
});
export const removeTodoAtom = atom(null, (get, set, id: number) => {
set(todos, removeTodo(get(todos), id));
});
First atom it will store the todo that we will type and the second it will store our todos items.
The addTodoAtom
and removeTodoAtom
it will be atoms the "update" the store of todos. But remember that we always be working as immutability as you can see on functions above.
I create that functions to facilitate the visualization of our atoms and to become more visible and lean what we are doing.
Notice that both addTodoAtom
and removeTodoAtom
are using as second parameter the get
, set
and update (that can be any value which we can pass as parameter)
. The get and set we can pass the atom that we will updating.
And we finish the state management! Fast and simple, isn’t it?
Now we will change the components to use our states and atoms.
First, let's change the ButtonChangeTheme
:
components/ButtonChangeTheme/index.tsx
import { useAtom } from "jotai";
import button from "./button.module.css";
import { changeTheme } from "../../atoms/theme/store";
interface ButtonChangeThemeProps {
label: string;
}
export const ButtonChangeTheme = ({ label }: ButtonChangeThemeProps) => {
const [, changeColor] = useAtom(changeTheme);
return (
<button className={button.btn} onClick={changeColor}>
{label}
</button>
);
};
We will use the useAtom
to get the atom of change theme. Remember when I said that the atom is very similar with useState. And when we created the atom of changeTheme
we pass null as first parameter. So we are working with the "set" of the "useState".
Now changing the Content
dummy component:
components/Content/index.tsx
import { useAtom } from "jotai";
import { isDark as isDarkState } from "../../atoms/theme/store";
interface ContentProps {
text: string;
}
export const Content = ({ text }: ContentProps) => {
const [isDark] = useAtom(isDarkState);
return (
<div
style={{
height: "30vh",
width: "100vw",
color: isDark ? "#fff" : "#111827",
backgroundColor: isDark ? "#111827" : "#fff",
}}
>
{text}
</div>
);
};
Now we are using the atom to get the theme.
Our FormTodo
component, let's just use the atoms that we created to store the todo that we type and add a new todo item:
components/FormTodo/index.tsx
import style from "./Form.module.css";
import { Button } from "../Button";
import { Input } from "../Input";
import { useAtom } from "jotai";
import { addTodoAtom, newTodoAtom } from "../../atoms/todo/store";
export const FormTodo = () => {
const [newTodo, setNewTodo] = useAtom(newTodoAtom);
const [, setAddTodo] = useAtom(addTodoAtom);
return (
<div className={style.formContainer}>
<Input
value={newTodo}
label="Todo"
onChange={(evt) => setNewTodo(evt.target.value)}
/>
<Button label="Add" onClick={setAddTodo} />
</div>
);
};
And finnaly, let's change the ListTodo
component to use the atom to remove a todo item and retrieve the todos:
components/ListTodo/index.tsx
import { useAtom } from "jotai";
import style from "./ListTodo.module.css";
import { removeTodoAtom, todos as todosStore } from "../../atoms/todo/store";
export const ListTodo = () => {
const [todos] = useAtom(todosStore);
const [, setRemoveTodo] = useAtom(removeTodoAtom);
return (
<ul>
{todos.map((todo) => (
<li className={style.item} key={todo.id}>
<label>{todo.text}</label>
<i
className={style.removeIcon}
onClick={() => setRemoveTodo(todo.id)}
/>
</li>
))}
</ul>
);
};
AND WE FINISHED, EASY PEASY, ISN'T IT?
Results
Now we can see how we use jotai, offering the minimum API to manage our state in a exact manner, without unnecessary re-renders.
The visual will not change but let's see 👀 using the React Dev Tools with highlighting render:
And the React Dev Tools Profiler:
Conclusion
Jotai offers a minimal API to manager our state. It simple and easy to maintain our state. Also, works with Next.js, Gatsby, Remix, and React Native.
Remember of the concept of atoms, because we will use this same concept on Recoil (library that Jotai was inspired).
I'm not saying that jotai is the best library and that you should use it in your project. So much so that the purpose of these articles is to compare the application and implementation of each state manager.
Jotai, also offers some utils packages, so if you are interested just access the documentation.
So in summary, in this article we saw how we can implement the jotai, creating atoms and useAtoms to manipulate our global state.
Some references:
That's all folks!
I hope you enjoyed it and added some knowledge. See you in the next parts! 💜
Top comments (0)