State Management on React [Part 4] - Recoil

kevin-uehara

Kevin Toshihiro Uehara

Posted on June 4, 2023

State Management on React [Part 4] - Recoil

Whaaats up, people! It's nice to have you here again!!!

The office happy gif

This is the fourth and penultimate part of articles of react management. How are you feeling with so much technologies? Amazing, isn’t it?
we are almost at the end of our journey!

I hope you guys liked the previous article where I talk about state management using the Jotai πŸ‘». I bring the concept of atoms and selectors and this will be very important for this article. Because, Jotai was inspired by Recoil, so the same concepts of atoms we will use here.

If you did not saw the previous part you can find here: Jotai State Management

We are going to create a simple application but we will be able to see the code of the technologies and compare the implementation.

Just to remember, let's get situated:

  • Context API (part 1)
  • Redux (part 2)
  • Jotai (part 3)
  • Recoil (part 4)
  • Zustand (part 5)

Summary:

Introduction

According to the documentation of Recoil:

Recoil defines a directed graph orthogonal to but also intrinsic and attached to your React tree. State changes flow from the roots of this graph (which we call atoms) through pure functions (which we call selectors) and into components.

Image of benefits of recoil

Recoil provide the minimal and simple API's to we manage our state (remember some other state manager cof cof jotai). This library is so simple that I don't have so much say aaas Jotai, 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 recoil
Enter fullscreen mode Exit fullscreen mode

Yeah, easy peasy!

Let's start adding the Provider of recoil, called RecoilRoot on our main three of components.

main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { RecoilRoot } from "recoil";
import { ButtonChangeTheme } from "./components/ButtonChangeTheme/index.tsx";
import { Content } from "./components/Content/index.tsx";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <RecoilRoot>
      <ButtonChangeTheme label="Change Theme" />
      <Content text="Hello World!" />
      <App />
    </RecoilRoot>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

I will create a directory on /src called store 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:

Directory our project image

I will start with the dark mode feature. We will create a file on store/theme/index.ts and create this code:

import { atom } from "recoil";

export const isDarkState = atom({
  key: "IsDark",
  default: false,
});
Enter fullscreen mode Exit fullscreen mode

That's it! What?
Yes! Minimalistic and simple.

We will provide a key and the value of the atom. But we will we see soon, that it's not so simple and there's magic here. We need to manipulate this state.

Now let's see how the todo store will it be.

Let's create onstore/theme two files. First the types types.ts (my preference) and the index.ts.

The types.ts contains just the type of our todo:

export interface ITodo {
  id: number;
  text: string;
  done: boolean;
}
Enter fullscreen mode Exit fullscreen mode

I will create a file on index.ts and the code will be:

import { atom } from "recoil";
import { ITodo } from "./types";

export const todoListState = atom<ITodo[]>({
  key: "TodoList",
  default: [],
});
Enter fullscreen mode Exit fullscreen mode

Woww, simple we created our "store" files using the atoms. Notice, that de default value it's an array, because we will provide a list o todo items.

Now we will change the components to use our states and atoms.

First, let's change the ButtonChangeTheme:

components/ButtonChangeTheme/index.tsx

import { useSetRecoilState } from "recoil";
import button from "./button.module.css";
import { isDarkState } from "../../store/theme";

interface ButtonChangeThemeProps {
  label: string;
}

export const ButtonChangeTheme = ({ label }: ButtonChangeThemeProps) => {
  const setChangeColor = useSetRecoilState(isDarkState);

  const handleChangeColor = () => {
    setChangeColor((color) => !color);
  };

  return (
    <button className={button.btn} onClick={handleChangeColor}>
      {label}
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

Here, we have the first difference of jotai. Recoil will provide a hook to set the state, called useSetRecoilState, where we will pass the atom that we created. And the business rules it will be stay on component.

Now changing the Content dummy component:

components/Content/index.tsx

import { useRecoilValue } from "recoil";
import { isDarkState } from "../../store/theme";

interface ContentProps {
  text: string;
}

export const Content = ({ text }: ContentProps) => {
  const isDark = useRecoilValue(isDarkState);

  return (
    <div
      style={{
        height: "30vh",
        width: "100vw",
        color: isDark ? "#fff" : "#111827",
        backgroundColor: isDark ? "#111827" : "#fff",
      }}
    >
      {text}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

To use the state, Recoil will provide the hook useRecoilValue and we will pass the atom that we created.

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. But again, the business logic will stay on component:

components/FormTodo/index.tsx

import style from "./Form.module.css";
import { Button } from "../Button";
import { Input } from "../Input";
import { useState } from "react";
import { useSetRecoilState } from "recoil";
import { todoListState } from "../../store/todo";
import { ITodo } from "../../store/todo/types";

export const FormTodo = () => {
  const [todo, setTodo] = useState("");
  const setTodoState = useSetRecoilState(todoListState);

  const handleAddTodo = () => {
    const newTodo: ITodo = {
      id: Math.random() * 1000,
      text: todo,
      done: false,
    };

    setTodoState((state) => [...state, newTodo]);
    setTodo("");
  };

  return (
    <div className={style.formContainer}>
      <Input
        value={todo}
        label="Todo"
        onChange={(evt) => setTodo(evt.target.value)}
      />
      <Button label="Add" onClick={handleAddTodo} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

To store the value of input value I will be using the useState. And to add on todo list we will copy the state, using the spread operator and call the hook useSetRecoilState.

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 { useRecoilValue, useSetRecoilState } from "recoil";
import style from "./ListTodo.module.css";
import { todoListState } from "../../store/todo";

export const ListTodo = () => {
  const todos = useRecoilValue(todoListState);
  const setTodoState = useSetRecoilState(todoListState);

  const handleRemoveTodo = (id: number) => {
    const removed = todos.filter((todo) => todo.id !== id);
    setTodoState(removed);
  };

  return (
    <ul>
      {todos.map((todo) => (
        <li className={style.item} key={todo.id}>
          <label>{todo.text}</label>
          <i
            className={style.removeIcon}
            onClick={() => handleRemoveTodo(todo.id)}
          />
        </li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

Here, we are using both hooks that I introduced to you useRecoilValue to get the todoList and useSetRecoilState to set the new list with the todo item removed.

and we finish one more time the same app LOL!

Results

Now we can see how we use Recoil, offering the minimum API to manage our state in a exact manner, without unnecessary re-renders.

But what I introduced to you it's just the basic of recoil. Read the documentation to see more details:

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

Recoil offers a minimal API to manager our state. It simple and easy to maintain our state.

It's easy to see jotai's inspiration

I'm not saying that recoil 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.

Recoil Documentation image

So in summary, in this article we saw how we can implement the recoil, creating atoms and manipulate our global state using the hooks that the recoil provides.

Some references:

That's all folks!

I hope you enjoyed it and added some knowledge. See you in the next and last part, where we will talk about Zustand! πŸ’œ

πŸ’– πŸ’ͺ πŸ™… 🚩
kevin-uehara
Kevin Toshihiro Uehara

Posted on June 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related