К основному содержанию
правило

zustand — правило для Cursor: что делает и как установить

Задаёт правила архитектуры хранилищ Zustand в React: типизацию стейта, деление на слайсы, селекторы и защиту от устаревших замыканий.

Установить в Cursor · Вручную: положить файлы Все способы и платформы ↓
.cursor/rules/zustand.mdc
Открыть на GitHub

Что делает zustand

Правило требует описывать явные интерфейсы TypeScript для состояния и экшенов, запрещая нетипизированные хранилища. Большие сторы делятся на логические слайсы вроде authSlice и uiSlice через тип StateCreator, а затем собираются в корневой useAppStore.

В коде запрещается экспортировать сырой объект create или вызывать setState в обход хуков: компоненты должны забирать данные только через кастомный хук с селекторами и функцией shallow. Для асинхронных операций и вычислений правило навязывает функциональные обновления вида set((state) => ...). Это защищает стейт от гонок и чтения устаревших значений из замыканий.

Какие задачи решает

  • разделить монолитный глобальный стейт на изолированные модульные слайсы
  • типизировать состояние и действия хранилища через строгие интерфейсы TypeScript
  • предотвратить лишние перерендеры компонентов через селекторы и shallow
  • устранить баги устаревших замыканий в асинхронных экшенах

Примеры запросов агенту

  • Создай слайс авторизации в src/store/slices/createAuthSlice.ts с полями user и token
  • Перепиши экшен инкремента в useCounterStore на функциональное обновление set()
  • Оптимизируй чтение данных профиля из useAppStore с помощью shallow

Как установить zustand

Вручную: положить файлы

.cursor/rules/zustand.mdc

Во frontmatter: description, globs, alwaysApply

По шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules

Вопросы о zustand

Как установить zustand в Cursor?

Создайте файл правила .cursor/rules/zustand.mdc в корне вашего проекта и добавьте туда содержимое инструкции.

Зачем нужно это правило?

Оно направляет ИИ-ассистента на создание модульных хранилищ с чистыми селекторами вместо свалки функций в одном файле.

Почему правило запрещает экспортировать сырой объект create?

Прямой вызов методов вроде setState вне хука обходит жизненный цикл React, поэтому правило требует отдавать наружу только готовый хук useAppStore.

Исходный текст

zustand.mdc · CC0-1.0
---
description: Definitive guidelines for using Zustand in React projects, focusing on type safety, modularity, performance, and maintainability with practical code examples.
globs: **/*.{js,jsx}
---
# zustand Best Practices

Zustand is our go-to for global state management due to its minimal API and built-in performance optimizations. This guide outlines the definitive patterns for using Zustand effectively in our projects, ensuring type safety, modularity, and optimal performance.

## 1. Typed Store Shape (TypeScript First)

Always define explicit TypeScript interfaces for your store's state and actions. This provides invaluable type safety and auto-completion across the application, preventing common runtime errors.

❌ BAD: Untyped store, prone to runtime errors
```typescript
import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));
```

✅ GOOD: Fully typed store for robust development
```typescript
import { create } from 'zustand';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
}

const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));
```

## 2. Slice-Based Organization

For scalable applications, organize your store into logical "slices" (e.g., `authSlice`, `uiSlice`). This keeps concerns separated, improves readability, and makes testing easier. Compose these slices into a single root store.

`src/store/types.ts`:
```typescript
import { StateCreator } from 'zustand';

// Define individual slice interfaces
export interface AuthSlice {
  user: { id: string; name: string } | null;
  token: string | null;
  login: (user: { id: string; name: string }, token: string) => void;
  logout: () => void;
}

export interface UISlice {
  isLoading: boolean;
  setLoading: (loading: boolean) => void;
}

// Combine all slice interfaces into the global AppState
export type AppState = AuthSlice & UISlice;

// Type for creating slices that will be composed into AppState
export type AppStateCreator<T> = StateCreator<AppState, [], [], T>;
```

`src/store/slices/createAuthSlice.ts`:
```typescript
import { AppStateCreator, AuthSlice } from '../types';

export const createAuthSlice: AppStateCreator<AuthSlice> = (set) => ({
  user: null,
  token: null,
  login: (user, token) => set({ user, token }),
  logout: () => set({ user: null, token: null }),
});
```

`src/store/slices/createUISlice.ts`:
```typescript
import { AppStateCreator, UISlice } from '../types';

export const createUISlice: AppStateCreator<UISlice> = (set) => ({
  isLoading: false,
  setLoading: (loading) => set({ isLoading: loading }),
});
```

`src/store/useAppStore.ts`:
```typescript
import { create } from 'zustand';
import { AppState } from './types';
import { createAuthSlice } from './slices/createAuthSlice';
import { createUISlice } from './slices/createUISlice';

export const useAppStore = create<AppState>()((...a) => ({
  ...createAuthSlice(...a),
  ...createUISlice(...a),
}));
```

## 3. Naming Conventions

Follow consistent naming for clarity and discoverability.

*   **Store Hook**: `use[Feature]Store` or `useAppStore` for the root.
*   **Actions**: Verb-oriented (e.g., `increment`, `setUser`, `fetchData`).
*   **Store Files**: Located under `src/store/` or `src/features/[feature]/store.ts`.
*   **Exports**: Only export the custom hook, never the raw `create` object.

❌ BAD: Inconsistent naming, exposing raw store
```typescript
// store.js
export const myStore = create(...); // Exposes raw store
export const useMyStore = myStore; // Bad naming, use `useMyStore` as the primary hook

// component.jsx
myStore.setState({ ... }); // Direct mutation outside hook, bypasses React lifecycle
```

✅ GOOD: Clear, consistent, and encapsulated
```typescript
// src/store/useAppStore.ts
export const useAppStore = create<AppState>(...);

// src/features/auth/useAuthStore.ts (if using feature-specific stores)
export const useAuthStore = create<AuthSlice>(...);

// component.tsx
import { useAppStore } from 'src/store/useAppStore';
const { user, login } = useAppStore();
```

## 4. Functional Updates to Prevent Stale Closures

Always use functional updates (`set(state => ...)`) when an action's new state depends on the current state. This prevents issues with stale closures in asynchronous operations or rapid updates.

❌ BAD: Potential stale closure, especially in async operations
```typescript
const useCounterStore = create<CounterState>((set, get) => ({
  count: 0,
  // ...
  incrementAsync: async () => {
    await someAsyncOperation();
    const currentCount = get().count; // 'currentCount' might be outdated if another update occurred
    set({ count: currentCount + 1 });
  },
}));
```

✅ GOOD: Robust functional update, `state` is always the latest
```typescript
const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  // ...
  incrementAsync: async () => {
    await someAsyncOperation();
    set((state) => ({ count: state.count + 1 }));
  },
}));
```

## 5. Selectors and Shallow Comparison for Performance

Consume only the necessary parts of the state using selectors. For objects or arrays, use `shallow` (or a custom equality function) to prevent unnecessary re-renders when only nested properties change.

❌ BAD: Re-renders component on any state change in the store
```typescript
const MyComponent = () => {
  const { user, token } = useAppStore(); // Re-renders if any part of AppState changes
  // ...
};
```

✅ GOOD: Optimized re-renders with selectors and `shallow`
```typescript
import { shallow } from 'zustand/shallow';
import { useAppStore } from 'src/store/useAppStore';

const UserProfile = () => {
  // Only re-renders if user.name or user.email changes
  const { name, email } = useAppStore(
    (state) => ({ name: state.user?.name, email: state.user?.email }),
    shallow
  );
  // ...
};

const AuthStatus = () => {
  // Only re-renders if token changes
  const token = useAppStore((state) => state.token);
  // ...
};
```

## 6. Essential Middleware Usage

Leverage Zustand's middleware for common concerns like persistence, devtools integration, and immutable updates.

*   **`persist`**: For local storage or IndexedDB. Always provide a `name` and consider `version` for migrations.
*   **`devtools`**: Integrate with Redux DevTools. Enable only in development.
*   **`immer`**: For simplified immutable updates, especially with deeply nested objects.

```typescript
import { create } from 'zustand';
import { persist, devtools, createJSONStorage } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface SettingsState {
  theme: 'light' | 'dark';
  notifications: { enabled: boolean; sound: boolean };
  setTheme: (theme: 'light' | 'dark') => void;
  toggleNotifications: () => void;
  toggleNotificationSound: () => void;
}

const useSettingsStore = create<SettingsState>()(
  devtools( // Enable devtools for debugging
    persist( // Persist state to storage
      immer((set) => ({ // Use immer for easier immutable updates
        theme: 'light',
        notifications: { enabled: true, sound: true },
        setTheme: (theme) => set({ theme }),
        toggleNotifications: () =>
          set((state) => {
            state.notifications.enabled = !state.notifications.enabled; // Direct mutation with immer
          }),
        toggleNotificationSound: () =>
          set((state) => {
            state.notifications.sound = !state.notifications.sound; // Direct mutation with immer
          }),
      })),
      {
        name: 'app-settings', // Unique name for storage key
        storage: createJSONStorage(() => localStorage), // Choose storage type
        version: 1, // Crucial for future migrations
        partialize: (state) => ({ theme: state.theme }), // Only persist 'theme'
      }
    ),
    { name: 'Settings Store', enabled: process.env.NODE_ENV === 'development' } // Devtools options
  )
);
```

## 7. Initializing Stores Outside Components

Declare your `create` calls at the module level (top of the file) to ensure a single, consistent store instance across your application. Never call `create` inside a React component.

❌ BAD: Creates new store instance on every render
```typescript
const MyComponent = () => {
  // This creates a NEW store instance every time MyComponent re-renders!
  const useLocalStore = create(() => ({ value: 0 }));
  const value = useLocalStore((state) => state.value);
  return <div>{value}</div>;
};
```

✅ GOOD: Single store instance, declared once
```typescript
// At the top of your store file (e.g., src/store/useCounterStore.ts)
const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

const MyComponent = () => {
  const count = useCounterStore((state) => state.count);
  return <div>{count}</div>;
};
```

## 8. Asynchronous Actions

Handle asynchronous operations directly within your store actions. Zustand doesn't require special middleware for async, keeping the API simple and direct.

```typescript
interface UserState {
  user: { id: string; name: string } | null;
  loading: boolean;
  error: string | null;
  fetchUser: (userId: string) => Promise<void>;
}

const useUserStore = create<UserState>((set) => ({
  user: null,
  loading: false,
  error: null,
  fetchUser: async (userId) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Failed

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Похожие правила IDE

Все правила IDE по теме «Фронтенд и UI» →
react-zustand-cursorrules-prompt-file PatrickJS Инструкция для Cursor по работе со стейт-менеджером Zustand в React и TypeScript. Задаёт архитектуру сторов, защищает от лишних ререндеров и отсекает серверный кэш от клиентских данных. ★ 41 028звёзд репо winui3 официальный github Правило задаёт рекомендации для WinUI 3 и Windows App SDK: от XAML и потоков до окон и MVVM. Пригодится, чтобы Copilot учитывал особенности настольных приложений Windows. ★ 40 478звёзд репо vue официальный github Правило задаёт Copilot стандарты Vue 3: Composition API, <script setup>, реактивность, Pinia и Vue Router. Пригодится, когда агент пишет компоненты в вашем стиле. ★ 40 478звёзд репо tanstack-start-shadcn-tailwind официальный github Правило задаёт Copilot стек TanStack Start, Shadcn/ui и Tailwind CSS и требует валидировать внешние данные через Zod. ★ 40 478звёзд репо tailwind-v4-vite официальный github Правило помогает подключить Tailwind CSS v4 к проекту на Vite через официальный плагин. Пригодится и для новой установки, и при переходе с v3. ★ 40 478звёзд репо svelte официальный github Направляет GitHub Copilot на стандарты Svelte 5 и SvelteKit 2: руны вместо старых сторов, типизация компонентов и безопасная работа с SSR. ★ 40 478звёзд репо power-bi-custom-visuals-development официальный github Правило для GitHub Copilot, которое подсказывает агенту, как писать кастомные визуалы Power BI на React, D3.js и TypeScript. ★ 40 478звёзд репо

Из того же набора240

Весь набор →
zsh Правило задаёт единый стиль для Zsh-скриптов и конфигурации в Cursor. Оно пригодится для проверок, читаемости и безопасной работы с оболочкой. ★ 3 580звёзд репо zod Правило для Cursor задаёт стандарты работы с Zod: строгий режим TypeScript, вывод типов из схем и проверка данных на входе. ★ 3 580звёзд репо xgboost Задаёт стандарты работы с библиотекой xgboost в Python: однократную сборку DMatrix, выбор tree_method='hist' и оформление докстрингов по стилю NumPy. ★ 3 580звёзд репо webpack Правило подсказывает Cursor, как организовать webpack-конфигурацию и настроить сборку. Пригодится при работе с JavaScript-проектом, где нужны отдельные настройки для разработки и production. ★ 3 580звёзд репо vue3 Правило задаёт агенту Cursor единый стиль для Vue 3: Composition API, TypeScript, ESLint и Prettier. Пригодится, если в проекте несколько разработчиков и код должен выглядеть одинаково. ★ 3 580звёзд репо vllm Правило задаёт рекомендации для Python-кода с vLLM: настройка окружения, организация инференса, производительность и наблюдаемость. Пригодится при разработке сервиса генерации на GPU. ★ 3 580звёзд репо vitest Правило задаёт Cursor, как писать юнит- и интеграционные тесты на Vitest: где держать файлы, как изолировать кейсы и мокать модули. ★ 3 580звёзд репо vite Задаёт стандарты проектов на Vite: требует чистый конфиг, прямой импорт модулей без barrel-файлов и использование import.meta.env. ★ 3 580звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой