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

🧠 Test Your Knowledge

What is the main unit of state in Recoil?