DEV Community

Cover image for State Management on React [Part 3] - Jotai
Kevin Toshihiro Uehara
Kevin Toshihiro Uehara

Posted on • Edited on

State Management on React [Part 3] - Jotai

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


Enter fullscreen mode Exit fullscreen mode

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>
);


Enter fullscreen mode Exit fullscreen mode

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:

Image description

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));
});


Enter fullscreen mode Exit fullscreen mode

AND THATS'S JUST IT!

Image description

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;
}


Enter fullscreen mode Exit fullscreen mode

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));
});


Enter fullscreen mode Exit fullscreen mode

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>
  );
};


Enter fullscreen mode Exit fullscreen mode

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>
  );
};


Enter fullscreen mode Exit fullscreen mode

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>
  );
};


Enter fullscreen mode Exit fullscreen mode

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>
);
};

Enter fullscreen mode Exit fullscreen mode




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:

Highlight of component rendering gif

And the React Dev Tools Profiler:

Profile viewer record of components rendering gif

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.

Jotai Documentation image

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)