Skip to content
Back to skills

Jotai Expert

ASecurity

Expert 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
developmenttypescriptgoreacttestinggitapifrontendperformancedocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill jotai-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Jotai Expert?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Jotai Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-jotai-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-jotai-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.