Recoil Adapter
Integrate React Dev Debugger with Recoil for comprehensive atom and selector tracking.
Installation
npm install react-dev-debugger recoil
Basic Setup
atoms.ts
import { atom } from 'recoil';
import { createRecoilAdapter } from 'react-dev-debugger/adapters/recoil';
// Define your atoms
export const countState = atom({
key: 'countState',
default: 0,
});
export const userState = atom({
key: 'userState',
default: null,
});
// Connect debugger
createRecoilAdapter({
atoms: [
{ atom: countState, name: 'count' },
{ atom: userState, name: 'user' },
],
});
Live Playground
Counter State
Count: 0
Name State
Length: 9
Todos State
Total: 0Completed: 0Pending: 0
Atom States
Recoil Adapter Configuration
import { atom, selector } from 'recoil';
import { createRecoilAdapter } from 'react-dev-debugger/adapters/recoil';
// Create atoms
const counterState = atom({
key: 'counterState',
default: 0,
});
const todosState = atom({
key: 'todosState',
default: [],
});
// Create selector
const todoStatsSelector = selector({
key: 'todoStatsSelector',
get: ({ get }) => {
const todos = get(todosState);
return {
total: todos.length,
completed: todos.filter(t => t.completed).length,
};
},
});
// Create adapter
const adapter = createRecoilAdapter({
name: 'My Store',
atoms: [counterState, todosState],
selectors: [todoStatsSelector],
});
// Subscribe to changes
adapter.subscribe((state) => {
console.log('Atom states:', state.atoms);
});Usage in Components
Counter.tsx
import { useRecoilState } from 'recoil';
import { countState } from './atoms';
export function Counter() {
const [count, setCount] = useRecoilState(countState);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Selectors
Track derived state with Recoil selectors:
atoms.ts
import { atom, selector } from 'recoil';
import { createRecoilAdapter } from 'react-dev-debugger/adapters/recoil';
export const todoListState = atom({
key: 'todoListState',
default: [],
});
export const todoListStatsState = selector({
key: 'todoListStatsState',
get: ({ get }) => {
const todoList = get(todoListState);
const totalNum = todoList.length;
const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
const totalUncompletedNum = totalNum - totalCompletedNum;
return {
totalNum,
totalCompletedNum,
totalUncompletedNum,
};
},
});
createRecoilAdapter({
atoms: [
{ atom: todoListState, name: 'todoList' },
{ atom: todoListStatsState, name: 'todoStats' },
],
});
Atom Families
Track dynamic atoms with atom families:
atoms.ts
import { atomFamily } from 'recoil';
import { createRecoilAdapter } from 'react-dev-debugger/adapters/recoil';
export const itemState = atomFamily({
key: 'itemState',
default: (id) => ({ id, name: '', completed: false }),
});
// Track specific instances
createRecoilAdapter({
atomFamilies: [
{ family: itemState, name: 'items' },
],
});
Complete Example
TodoApp.tsx
import { useState } from 'react';
import {
RecoilRoot,
useRecoilState,
useRecoilValue,
useSetRecoilState,
} from 'recoil';
import { todoListState, todoListStatsState } from './atoms';
function TodoList() {
const [todoList, setTodoList] = useRecoilState(todoListState);
const [inputValue, setInputValue] = useState('');
const stats = useRecoilValue(todoListStatsState);
const addItem = () => {
if (inputValue.trim()) {
setTodoList([
...todoList,
{
id: Date.now(),
text: inputValue,
isComplete: false,
},
]);
setInputValue('');
}
};
const toggleItem = (id) => {
setTodoList(
todoList.map((item) =>
item.id === id ? { ...item, isComplete: !item.isComplete } : item
)
);
};
return (
<div>
<h1>Recoil Todo List</h1>
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addItem()}
/>
<button onClick={addItem}>Add</button>
</div>
<ul>
{todoList.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.isComplete}
onChange={() => toggleItem(todo.id)}
/>
<span style={{
textDecoration: todo.isComplete ? 'line-through' : 'none'
}}>
{todo.text}
</span>
</li>
))}
</ul>
<div>
Total: {stats.totalNum} | Completed: {stats.totalCompletedNum} |
Active: {stats.totalUncompletedNum}
</div>
</div>
);
}
export default function App() {
return (
<RecoilRoot>
<TodoList />
</RecoilRoot>
);
}
Async Selectors
Track asynchronous data fetching:
atoms.ts
import { atom, selector } from 'recoil';
export const userIDState = atom({
key: 'userID',
default: 1,
});
export const userDataState = selector({
key: 'userData',
get: async ({ get }) => {
const userId = get(userIDState);
const response = await fetch(`/api/user/${userId}`);
return response.json();
},
});
Configuration Options
interface RecoilAdapterOptions {
// Display name in debugger
name?: string;
// Track specific atoms
atoms?: Array<{ atom: RecoilState; name: string }>;
// Track atom families
atomFamilies?: Array<{ family: any; name: string }>;
// Maximum snapshots
maxSnapshots?: number;
}
Debugging Tips
View Atom Dependencies
The dependency graph shows which selectors depend on which atoms.
Async State
Suspense boundaries and loading states are tracked in the timeline.
Time-Travel
Step through atom value changes to debug state transitions.