Back to skills
SKILL.md
Jotai Expert
ASecurityExpert in Jotai state management with TypeScript, derivation, persistence, and performance best practices. Use when you need deep expertise in jotai.
- 9 stars
- 0 votes
- 0 copies
- 2 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill jotai-expert --agent claude-codeAre you the author of Jotai Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-jotai-expert)---
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)
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.