Recoil is yet another state management library for React, I just wanted to give it a try so I decided to re-create my other post's sample (MobX with React and TypeScript
) in Recoil.
Recoil has pretty simple API to work with, the functionality is the same as other state management library but the names are different, Recoil seems to be a bit better in terms of code boilerplate. It has two important concepts to know:
Atoms: Atoms are units of state. They're updateable and subscribable: when an atom is updated, each subscribed component is re-rendered with the new value. They can be created at runtime, too. Atoms can be used in place of React local component state. If the same atom is used from multiple components, all those components share their state.
Selector: A selector is a pure function that accepts atoms or other selectors as input. When these upstream atoms or selectors are updated, the selector function will be re-evaluated. Components can subscribe to selectors just like atoms, and will then be re-rendered when the selectors change.
Creating a Simple Todo App with Recoil and React
let's create a simple todo app using React:
mkdit recoil_sample && cd recoil_sample
npx create-react-app . --typescript
yarn add bootstrap --save
Now go to index.tsx
and import bootstrap.css
:
import "bootstrap/dist/css/bootstrap.css"
Now we'll install the needed dependencies:
yarn add recoil uuid @types/uuid --save
Now let's create a store inside the project
import { atom, selector } from "recoil";
export interface Todo {
id?: string;
title: string;
completed: boolean;
}
export const todosState = atom({
key: "todos",
default: [] as Todo[],
});
export const infoValue = selector({
key: "infoValue",
get: ({ get }) => ({
total: get(todosState).length,
completed: get(todosState).filter((todo) => todo.completed).length,
notCompleted: get(todosState).filter((todo) => !todo.completed).length,
}),
});
Now create a new folder called components in the src
directory and add TodoAdd.tsx
and TodoList.tsx
:
TodoAdd
import { useState } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { infoValue, todosState } from "../store";
import { v4 } from "uuid";
const AddTodo = () => {
const [title, setTitle] = useState("");
const [todos, setTodos] = useRecoilState(todosState);
const info = useRecoilValue(infoValue);
return (
<>
<div className="alert alert-primary">
<div className="d-inline col-4">
Total items:
<span className="badge badge-info">{info.total}</span>
</div>
<div className="d-inline col-4">
Finished items:
<span className="badge badge-info">{info.completed}</span>
</div>
<div className="d-inline col-4">
Unfinished items:
<span className="badge badge-info">{info.notCompleted}</span>
</div>
</div>
<div className="form-group">
<input
className="form-control"
type="text"
value={title}
placeholder="Todo title..."
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<div className="form-group">
<button
className="btn btn-primary"
onClick={(_) => {
setTodos([...todos, { id: v4(), title: title, completed: false }]);
setTitle("");
}}
>
Add Todo
</button>
</div>
</>
);
};
export default AddTodo;
TodoList
import { useRecoilState } from "recoil";
import { todosState } from "../store";
const TodoList = () => {
const [todos, setTodos] = useRecoilState(todosState);
const toggleTodo = (id: string) =>
setTodos(
todos.map((todo) => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed,
};
}
return todo;
})
);
const removeTodo = (id: string) =>
setTodos(todos.filter((todo) => todo.id !== id));
return (
<>
<div className="row">
<table className="table table-hover">
<thead className="thead-light">
<tr>
<th>Title</th>
<th>Completed?</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{todos.map((todo) => (
<tr key={todo.id}>
<td>{todo.title}</td>
<td>{todo.completed ? "✅" : ""}</td>
<td>
<button
className="btn btn-sm btn-info"
onClick={(_) => toggleTodo(todo.id!)}
>
Toggle
</button>
<button
className="btn btn-sm btn-danger"
onClick={(_) => removeTodo(todo.id!)}
>
Remove
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</>
);
};
export default TodoList;
That's it, now you can run the project and manage your todo items:
Here's the source for the project.
Top comments (2)
Thanks for the tutorial on this mate. Just commenting on the
atom
constructor. I've recently learned that you can cast the function call so you don't need to use theas
operator. Something like this:Other than that, this is great content. Much appreciated!
Thank you for pointing it out. :) I am glad to know you enjoyed the content.