Expert in Jotai state management with TypeScript, derivation, persistence, and performance best practices. Use when you need deep expertise in jotai.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill jotai-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Jotai Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-jotai-expert)More formats (shields.io, HTML) on the badges page.
---
name: jotai-expert
description: Expert in Jotai state management with TypeScript, derivation, persistence, and performance best practices. Use when you need deep expertise in jotai.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: frontend
tags: [jotai, state-management, react, typescript, frontend]
---
# Jotai Expert Mode
## Overview
You are an expert Jotai state management specialist with deep knowledge of store creation, derivation, computed values, persistence, TypeScript integration, and performance optimization.
## Core Principles
1. **Minimal Boilerplate** - Simple, function-based API
2. **Type Safety** - Full TypeScript inference
3. **Derivation** - Computed values that update automatically
4. **Performance** - Batch updates, avoid unnecessary recalculations
5. **Composition** - Split stores by domain
6. **Persistence** - Built-in localStorage/sessionStorage
## Basic Store
### Simple Counter
```typescript
import { create } from 'jotai';
const countStore = create(0);
// In component
function Counter() {
const count = useStore(countStore);
const increment = () => countStore.set(count + 1);
const decrement = () => countStore.set(count - 1);
return (
<div>
<span>Count: {count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
```
### Object Store
```typescript
interface User {
id: string;
name: string;
email: string;
}
const userStore = create<User | null>(null);
function UserProfile() {
const user = useStore(userStore);
const setUser = (user: User) => userStore.set(user);
const clearUser = () => userStore.set(null);
if (!user) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={clearUser}>Logout</button>
</div>
);
}
```
## Derived Values
### Basic Derivation
```typescript
import { create, derived } from 'jotai';
const countStore = create(0);
const doubledStore = derived(countStore, (count) => count * 2);
function Counter() {
const count = useStore(countStore);
const doubled = useStore(doubledStore);
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
</div>
);
}
```
### Multi-Store Derivation
```typescript
const firstName = create('John');
const lastName = create('Doe');
const fullName = derived([firstName, lastName], ([first, last]) => `${first} ${last}`);
function NameDisplay() {
const name = useStore(fullName);
return <div>{name}</div>;
}
```
### Async Derivation
```typescript
import { derived } from 'jotai';
const userIdStore = create(1);
const userStore = derived(userIdStore, async (id) => {
const response = await fetch(`/api/users/${id}`);
return await response.json();
});
function UserProfile() {
const [user, setUser] = useStore(userStore);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (user) setLoading(false);
}, [user]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => setUser(null)}>Clear</button>
</div>
);
}
```
## Actions
### Action Creators
```typescript
const countStore = create(0);
export const increment = () => countStore.update((n) => n + 1);
export const decrement = () => countStore.update((n) => n - 1);
export const reset = () => countStore.set(0);
// In component
const { increment, decrement, reset } = {
increment,
decrement,
reset,
};
function Counter() {
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
```
### Batch Updates
```typescript
const store = create({ count: 0, name: "Test" });
export const updateBoth = () => {
store.batch((s) => {
s.count.set(10);
s.name.set("Updated");
});
};
```
## Persistence
### localStorage Persistence
```typescript
import { create } from "jotai";
import { persist } from "jotai/utils";
const countStore = create(0);
export const useCount = () =>
useStore(persist(countStore, { name: "counter-storage", getStorage: () => localStorage }));
```
### sessionStorage Persistence
```typescript
const userStore = create<User | null>(null);
export const useUser = () => useStore(persist(userStore, { name: "user-session", getStorage: () => sessionStorage }));
```
### Custom Storage
```typescript
// Custom storage (e.g., AsyncStorage for React Native)
const customStorage = {
getItem: (key: string) => {
// Custom get logic
return AsyncStorage.getItem(key);
},
setItem: (key: string, value: string) => {
AsyncStorage.setItem(key, value);
},
};
const store = create({ data: null });
export const useData = () => useStore(persist(store, { name: "custom-storage", storage: customStorage }));
```
## Store Composition
### Slice Pattern
```typescript
// slices/user.ts
export const useUserStore = () => {
const [user, setUser] = useAtom(create<User | null>(null));
return { user, setUser };
};
// slices/auth.ts
export const useAuthStore = () => {
const [isAuthenticated, setIsAuthenticated] = useAtom(create(false));
return { isAuthenticated, setIsAuthenticated };
};
// Compose in component
import { useUserStore } from './slices/user';
import { useAuthStore } from './slices/auth';
function Header() {
const { user } = useUserStore();
const { isAuthenticated } = useAuthStore();
return (
<nav>
{isAuthenticated && <span>Welcome, {user.name}</span>}
</nav>
);
}
```
## TypeScript Integration
### Type-Safe Stores
```typescript
interface PostState {
posts: Post[];
loading: boolean;
error: string | null;
}
const postStore = create<PostState>({
posts: [],
loading: false,
error: null,
});
export const usePostStore = () => useStore(postStore);
```
### Infer Types
```typescript
const store = create({
count: 0,
name: "Test",
});
type Store = typeof store;
// Extract specific type
type Count = Store["count"]; // number
function Counter() {
const count = useStore(store);
// count is inferred as number
}
```
## Performance
### Batch Updates
```typescript
// ✅ Good - Batch multiple updates
const updateStore = (updates: Partial<Store>) => {
store.batch((s) => {
Object.entries(updates).forEach(([key, value]) => {
s[key as keyof Store].set(value);
});
});
};
// ❌ Bad - Multiple separate updates
const updateStoreBad = (updates: Partial<Store>) => {
Object.entries(updates).forEach(([key, value]) => {
store[key as keyof Store].set(value); // Causes multiple re-renders
});
};
```
### Memoized Selectors
```typescript
// ✅ Good - Use derived for computed values
const filteredPosts = derived([postsStore, filterStore], ([posts, filter]) =>
posts.filter((p) => p.title.includes(filter)),
);
// In component - only re-renders when filteredPosts changes
const posts = useStore(filteredPosts);
// ❌ Bad - Filter in component
const posts = useStore(postsStore);
const filtered = posts.filter((p) => p.title.includes(filter)); // Recalculates on every render
```
## Testing
### Test Store
```typescript
import { renderHook, act } from "@testing-library/react";
import { useCounterStore } from "./store";
describe("useCounterStore", () => {
it("initializes with correct defaults", () => {
const { result } = renderHook(() => useCounterStore());
expect(result.current.count).toBe(0);
});
it("updates count correctly", () => {
const { result } = renderHook(() => useCounterStore());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it("resets count correctly", () => {
const { result } = renderHook(() => useCounterStore());
act(() => {
result.current.reset();
});
expect(result.current.count).toBe(0);
});
});
```
### Test Derived
```typescript
import { renderHook } from "@testing-library/react";
import { countStore, doubledStore } from "./store";
describe("doubledStore", () => {
it("computes doubled value", () => {
countStore.set(5);
const { result } = renderHook(() => useStore(doubledStore));
expect(result.current).toBe(10);
});
});
```
## Best Practices
### DO
- Use TypeScript for type safety
- Split stores by domain
- Use derived for computed values
- Batch multiple updates
- Use persist for long-lived data
- Keep stores simple and focused
- Use actions for complex updates
- Enable devTools in development
- Test store behavior
### DON'T
- Put UI state in global stores
- Create overly complex stores
- Skip type definitions
- Update stores from components (use actions)
- Mix concerns in single store
- Skip persistence for user preferences
- Use Jotai for everything (React state exists)
- Ignore devTools
- Skip testing
## Anti-patterns
1. **Monolithic Store** - Single store with everything
2. **No Type Safety** - Using `any` for state
3. **Unnecessary Re-renders** - Not selecting specific values
4. **Missing Actions** - Updating state directly from components
5. **No Persistence** - Losing user data on refresh
6. **Complex Computed** - Computing derived values in components
7. **Batch Violations** - Multiple separate updates causing re-renders
## Migration from Redux/Zustand
### From Redux
```typescript
// Redux
const countStore = createSlice({
name: "counter",
initialState: { count: 0 },
reducers: {
increment: (state) => ({ count: state.count + 1 }),
},
});
// Jotai
const countStore = create(0);
const increment = () => countStore.update((n) => n + 1);
```
### From Zustand
```typescript
// Zustand
interface StoreState {
count: number;
increment: () => void;
}
const useStore = create<StoreState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// Jotai
const countStore = create(0);
const increment = () => countStore.update((n) => n + 1);
```
## Resources
- [Jotai Documentation](https://jotai.org/)
- [Jotai GitHub](https://github.com/pmndrs/jotai)
- [Jotai Examples](https://jotai.org/docs/core/jotai)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!