Recoil
Modern state management for React applications
⚛️ What is Recoil?
Recoil is a state management library for React that provides a simple way to share state across components. It uses atoms and selectors to manage global state efficiently with minimal boilerplate code.
// Simple Recoil example
import { atom, useRecoilState } from 'recoil';
const countState = atom({
key: 'countState',
default: 0
});
Key Recoil Concepts
Atoms
Units of state that components can subscribe to
const textState = atom({
key: 'textState',
default: ''
});
Selectors
Derived state based on atoms or other selectors
const charCountState = selector({
key: 'charCountState',
get: ({get}) => get(textState).length
});
Hooks
React hooks to read and write Recoil state
const [count, setCount] =
useRecoilState(countState);
RecoilRoot
Provider component for Recoil state
<RecoilRoot>
<App />
</RecoilRoot>
🔹 Setting Up Recoil
Install Recoil and wrap your app with RecoilRoot to enable state management across all components in your React application.
# Install Recoil
npm install recoil
// App.js - Wrap your app
import { RecoilRoot } from 'recoil';
function App() {
return (
<RecoilRoot>
<YourComponents />
</RecoilRoot>
);
}
🔹 Creating Atoms
Atoms represent pieces of state. Any component can read from and write to an atom. Components that use an atom will re-render when it changes.
// state/atoms.js
import { atom } from 'recoil';
// Counter atom
export const counterState = atom({
key: 'counterState', // unique ID
default: 0 // default value
});
// User atom
export const userState = atom({
key: 'userState',
default: { name: '', email: '' }
});
🔹 Using Atoms in Components
Use useRecoilState hook to read and write atom values, similar to useState. Use useRecoilValue for read-only access and useSetRecoilState for write-only access.
// Counter.js
import { useRecoilState } from 'recoil';
import { counterState } from './state/atoms';
function Counter() {
const [count, setCount] = useRecoilState(counterState);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
🔹 Creating Selectors
Selectors compute derived state from atoms or other selectors. They automatically update when their dependencies change, making them perfect for calculations and transformations.
// state/selectors.js
import { selector } from 'recoil';
import { counterState } from './atoms';
export const doubleCountState = selector({
key: 'doubleCountState',
get: ({get}) => {
const count = get(counterState);
return count * 2;
}
});
// Using selector in component
import { useRecoilValue } from 'recoil';
function Display() {
const doubled = useRecoilValue(doubleCountState);
return <p>Doubled: {doubled}</p>;
}
🔹 Async Selectors
Selectors can be asynchronous to fetch data from APIs. Recoil handles loading states automatically, making data fetching simple and declarative in your components.
// Async data fetching
export const userDataState = selector({
key: 'userDataState',
get: async () => {
const response = await fetch('/api/user');
return response.json();
}
});
// Component with Suspense
import { Suspense } from 'react';
function UserProfile() {
const user = useRecoilValue(userDataState);
return <div>{user.name}</div>;
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile />
</Suspense>
);
}
🔹 Atom Families
Atom families create multiple related atoms dynamically. Perfect for managing collections of items where each item needs its own state, like todo lists or form fields.
import { atomFamily } from 'recoil';
// Create atom for each todo item
export const todoItemState = atomFamily({
key: 'todoItemState',
default: (id) => ({
id,
text: '',
completed: false
})
});
// Usage
function TodoItem({ id }) {
const [todo, setTodo] = useRecoilState(todoItemState(id));
return (
<div>
<input
value={todo.text}
onChange={(e) => setTodo({...todo, text: e.target.value})}
/>
</div>
);
}
🔹 Complete Example
Here's a complete todo app demonstrating atoms, selectors, and hooks working together to manage application state efficiently across multiple components.
// state.js
import { atom, selector } from 'recoil';
export const todoListState = atom({
key: 'todoListState',
default: []
});
export const todoStatsState = selector({
key: 'todoStatsState',
get: ({get}) => {
const todos = get(todoListState);
return {
total: todos.length,
completed: todos.filter(t => t.completed).length
};
}
});
// TodoApp.js
function TodoApp() {
const [todos, setTodos] = useRecoilState(todoListState);
const stats = useRecoilValue(todoStatsState);
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
return (
<div>
<h1>Todos: {stats.completed}/{stats.total}</h1>
<input onKeyPress={(e) => {
if (e.key === 'Enter') addTodo(e.target.value);
}} />
{todos.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
);
}