Why are Errors in Software Code Called “Bugs”?
4th May 2024Google Gemini
8th May 2024useRecoil – If you’re a React developer, you’ve probably used a library for managing state in your React applications. And you’ve likely heard of Redux “the state management” library for React.
For a long time, Redux was the only reliable and most widely-adopted solution for state management in React applications. And Redux has proven its use cases in big applications.
But the main problem that developers often face with Redux was the overall developer experience. In early versions of Redux, you had to manually set up your global data store and manually connect each component to consume it and update the global state. Basically, it used to take a lot of both time and effort for developers to set up and use Redux in their applications.
Over time Redux has improved and now it too provides simple plugin solutions like redux-toolkit. But now there are even much simpler state management solutions available for React like zustand, recoil, and react-query just to name a few.
In this article, we’ll have a look at how to set up and use Recoil in your React applications by building a simple traditional todo app.
How to Install Recoil
Let’s start by installing the library. If you are working on your local computer, you can install Recoil using npm
or yarn
.
npm i recoil
// or
yarn add recoil
How to Add Recoil’s Root Component
The first thing that you need to do is wrap your entire application with the RecoilRoot
component provided by recoil
.
Since Recoil uses a 100% hook-based approach, it’s good to wrap your entire application with the root component provided by Recoil so that you can access your application state from any component.
You can simply do this by importing and adding RecoilRoot
in your index.js (entry file). This is how your index.js will look after you add it:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { RecoilRoot } from "recoil";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</StrictMode>,
rootElement
);
How to Create an Atom in Recoil
After this, we need to create an atom. An atom in Recoil is simply an isolated piece of memory that holds data. You can create as many atoms as you want.
For example, say you are creating a social media application where users can bookmark a certain post. To store users’ bookmarked posts, you can have a separate atom holding just the data for bookmarks.
When some data changes in the atom – for example, the user bookmarks a post – it will re-render components subscribed to or using that atom.
This is where the performance part of recoil
comes into play. Recoil will make sure that only those components are being re-rendered that are subscribed to that specific atom.
Creating an atom is extremely easy. Create a src/recoil/atom/todoAtom.js
file and add the following code:
import { atom } from "recoil";
export const todoListAtom = atom({
key: "todoListState",
default: [],
});
How to Create Your First Atom
You just need to import the atom
function from recoil
. This function takes an object as its argument.
The first entry in that object is key
. This is a unique string that will represent this specific atom. default
is the initial state of this atom. And that’s it. That’s all you need to do to set it up.
Make sure to export todoListAtom
as we will be using it to reference this atom.
How to Add Data to an Atom
Now let’s create an input where the user can type in their todo. Create components/TodoItemCreator.js
.
In this component, we have an input where the user will type and we will see how simple it is to add a new todo in the atom. Later we will see how all the components which are using the same atom update to show the newly added todo. You can be as creative as you want while styling the input.
Here I will only show how we can use the useRecoilState
hook (it’s provided by the recoil
library to get the current state of the data inside the atom) and a handy function to update the state.
If you have used useState
in React, this will look quite identical to what you’re used to in your local component state. The useRecoilState
hook takes an atom as the argument.
import { useState } from "react";
import { useRecoilState } from "recoil";
import { todoListAtom } from "../recoil/atoms/todoAtom";
import { generateUID } from "../utils/uuid";
export const TodoItemCreator = () => {
const [inputValue, setInputValue] = useState("");
const [_, setTodoList] = useRecoilState(todoListAtom);
const onChange = (event) => {
setInputValue(event.target.value);
};
const addTodoItem = () => {
if (inputValue) {
setTodoList((oldTodoList) => [
...oldTodoList,
{
id: generateUID(),
text: inputValue,
isComplete: false
}
]);
setInputValue("");
}
};
return (
<div className="todo-creator">
<input type="text" value={inputValue} onChange={onChange} />
<button className="add-btn" onClick={addTodoItem}>
Add Task
</button>
</div>
);
};
When the user types in the input and clicks the Add Task
button, an addTodoItem
function is called. This function simply calls the setTodoList
function given by the hook.
Since it is recommended to never update your global state directly, instead create a shallow copy of previous todos and add a new one. In the above code snippet, generateUID
is just a utility function that will return a uuidv4
unique id to return a random unique id that we will later use to update a simple todo from a list of todos
.
How to Consume Data from the Atom
Now let’s create a component to display a todo in a list and enable the user to update, delete, or mark the todos as done. Create src/components/TodoMain.js
.
import { useRecoilValue } from "recoil";
import { TodoItemCreator } from "./TodoItemCreator";
import { TodoItem } from "./TodoItem";
import { todoListAtom } from "../recoil/atoms/todoAtom";
import "./todo.css";
export const TodoMain = () => {
const todoList = useRecoilValue(todoListAtom);
return (
<div className="parent-container">
<div>
<TodoItemCreator />
{todoList.length > 0 && (
<div className="todos-list">
{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</div>
)}
</div>
</div>
);
};
useRecoilValue
is a hook provided by recoil
that only returns the current state of date in the atom. We will use this hook to get all todos and map
over them to display them on the screen.
How to Update Data in Atom
TodoItem
is a component that uses the same useRecoilState
hook and some helper functions to find and update the state of a specific todo.
import { useRecoilState } from "recoil";
import { todoListAtom } from "../recoil/atoms/todoAtom";
export const TodoItem = ({ item }) => {
const [todoList, setTodoList] = useRecoilState(todoListAtom);
const index = todoList.findIndex((listItem) => listItem === item);
const editItemText = (event) => {
const newList = replatItemAtIndex(todoList, index, {
...item,
text: event.target.value
});
setTodoList(newList);
};
const toggleItemCompletion = () => {
const newList = replatItemAtIndex(todoList, index, {
...item,
isComplete: !item.isComplete
});
setTodoList(newList);
};
const deleteItem = () => {
const newList = removeItemAtIndex(todoList, index);
setTodoList(newList);
};
return (
<div className="container">
<input
className={item.isComplete.toString() === "true" && "done-task"}
type="text"
value={item.text}
onChange={editItemText}
/>
<input
type="checkbox"
checked={item.isComplete}
onChange={toggleItemCompletion}
/>
<button className="del-btn" onClick={deleteItem}>
X
</button>
</div>
);
};
const replatItemAtIndex = (arr, index, newValue) => {
return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
};
const removeItemAtIndex = (arr, index) => {
return [...arr.slice(0, index), ...arr.slice(index + 1)];
};
And that’s it. With two hooks and one function, you can handle all the state management requirements of your React applications. Recoil’s power is its simple and beginner-friendly API and performance.