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

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

Задаёт стандарты реактивного состояния на MobX: запрещает устаревшие декораторы и настраивает RootStore через React Context.

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

Что делает react-mobx

Правило ориентирует Cursor на архитектуру React с MobX без устаревших декораторов. Состояние делится на доменные классы с вызовом makeObservable в конструкторе. Все хранилища объединяются в RootStore, который раздаётся дереву компонентов через стандартный React Context и хук useStore.

Фронтенд оптимизируется под библиотеку mobx-react-lite: компоненты оборачиваются в observer, а чтение наблюдаемых полей выносится прямо в JSX без промежуточных переменных. Правило следит за разделением интерфейса на мелкие части и запрещает указывать индексы массивов в качестве key.

Если в проекте ещё остались устаревшие декораторы @observable, правило сразу предложит переписать их на явный makeObservable ради совместимости с React Compiler.

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

  • переписать хранилища с устаревших декораторов на makeObservable
  • организовать передачу RootStore через React Context и хук useStore
  • сократить лишние рендеры оборачиванием компонентов в observer из mobx-react-lite
  • предотвратить раннее чтение observable-полей вне JSX

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

  • Создай доменное хранилище CartStore с явным makeObservable и экшенами
  • Оберни список пользователей в observer и настрой чтение полей прямо внутри разметки
  • Собери RootStore и настрой StoreProvider с хуком useStore

Как установить react-mobx

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

.cursor/rules/react-mobx.mdc

Во frontmatter: description, globs, alwaysApply

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

Вопросы о react-mobx

Как установить react-mobx в Cursor?

Создайте файл .cursor/rules/react-mobx.mdc в корне репозитория и скопируйте в него текст правила.

Что делает правило react-mobx?

Оно задаёт правила для генерации кода на React и MobX: требует явную декларацию полей через makeObservable, запрещает декораторы и контролирует позднее чтение свойств в JSX.

Почему правило запрещает декораторы MobX?

Декораторы считаются устаревшими, поэтому правило требует вызывать makeObservable в конструкторе класса ради совместимости с современными сборками и React Compiler.

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

react-mobx.mdc · CC0-1.0
---
description: This guide provides opinionated, actionable best practices for building scalable and performant React applications using MobX, focusing on modern patterns and React Compiler compatibility.
globs: **/*.{js,jsx,ts,tsx}
---
# react-mobx Best Practices

MobX, paired with React, offers a powerful and low-boilerplate approach to state management. By adhering to these guidelines, your team will build predictable, scalable applications ready for React 19 and the React Compiler.

## 1. Store Design and Organization

Organize your application state into domain-specific stores, composed into a single root store.

### 1.1. Class-Based Stores with `makeObservable`

Define each domain area as a class. Use `makeObservable` in the constructor to explicitly declare observables, computed values, and actions. This is the modern, explicit way to define your reactive state.

❌ **BAD: Implicit MobX decorators (legacy)**
```javascript
// src/stores/UserStore.js
import { observable, action, computed } from 'mobx';

class UserStore {
  @observable users = [];
  @observable isLoading = false;

  @computed get activeUsers() {
    return this.users.filter(u => u.isActive);
  }

  @action addUser(user) {
    this.users.push(user);
  }
}
```

✅ **GOOD: Explicit `makeObservable` (modern)**
```typescript
// src/stores/UserStore.ts
import { makeObservable, observable, action, computed } from 'mobx';

interface User {
  id: string;
  name: string;
  isActive: boolean;
}

export class UserStore {
  users: User[] = [];
  isLoading: boolean = false;

  constructor() {
    makeObservable(this, {
      users: observable,
      isLoading: observable,
      activeUsers: computed,
      addUser: action,
      fetchUsers: action,
    });
  }

  get activeUsers(): User[] {
    return this.users.filter(u => u.isActive);
  }

  addUser(user: User) {
    this.users.push(user);
  }

  async fetchUsers() {
    this.isLoading = true;
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 500));
    this.users = [{ id: '1', name: 'Alice', isActive: true }];
    this.isLoading = false;
  }
}
```

### 1.2. Root Store Composition

Create a `RootStore` that instantiates and holds references to all other stores. This provides a single entry point for your application's state and facilitates inter-store communication.

```typescript
// src/stores/RootStore.ts
import { UserStore } from './UserStore';
import { AuthStore } from './AuthStore';

export class RootStore {
  userStore: UserStore;
  authStore: AuthStore;

  constructor() {
    this.authStore = new AuthStore(this); // Pass root store for cross-store access
    this.userStore = new UserStore();
  }
}

// Export a singleton instance for convenience, or create one in your app entry.
export const rootStore = new RootStore();
```

### 1.3. Consistent Naming Conventions

Maintain clear naming for stores, observables, and actions.

*   **Stores**: PascalCase (e.g., `UserStore`, `ProductStore`).
*   **Observable Fields**: camelCase (e.g., `users`, `isLoading`).
*   **Computed Values**: camelCase, often prefixed with `get` if a getter (e.g., `get activeUsers`).
*   **Actions**: Verb-prefixed camelCase (e.g., `addUser`, `fetchUsers`).

## 2. Context-Based Store Injection

Avoid prop-drilling by providing your root store via React Context and consuming it with a custom hook.

```typescript
// src/contexts/StoreContext.ts
import React, { createContext, useContext } from 'react';
import { RootStore, rootStore as defaultRootStore } from '../stores/RootStore';

export const StoreContext = createContext<RootStore>(defaultRootStore);

export const StoreProvider: React.FC<{ store?: RootStore }> = ({ children, store }) => {
  return (
    <StoreContext.Provider value={store || defaultRootStore}>
      {children}
    </StoreContext.Provider>
  );
};

export const useStore = (): RootStore => {
  const store = useContext(StoreContext);
  if (!store) {
    throw new Error('useStore must be used within a StoreProvider.');
  }
  return store;
};
```

**Usage in `App.tsx`:**
```typescript
// src/App.tsx
import React from 'react';
import { StoreProvider } from './contexts/StoreContext';
import { rootStore } from './stores/RootStore';
import UserList from './components/UserList';

function App() {
  return (
    <StoreProvider store={rootStore}>
      <div className="App">
        <h1>My MobX App</h1>
        <UserList />
      </div>
    </StoreProvider>
  );
}

export default App;
```

## 3. Observer Integration and Component Granularity

Wrap components that read observable data with `observer` to enable automatic re-rendering. Design small, focused components to maximize MobX's fine-grained reactivity.

### 3.1. Wrap Components with `observer`

Use the `observer` HOC (or `useObserver` if preferred) to make your functional components react to observable changes.

```typescript
// src/components/UserList.tsx
import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite'; // Use mobx-react-lite for functional components
import { useStore } from '../contexts/StoreContext';

const UserList: React.FC = observer(() => {
  const { userStore } = useStore();

  useEffect(() => {
    userStore.fetchUsers();
  }, [userStore]);

  if (userStore.isLoading) {
    return <div>Loading users...</div>;
  }

  return (
    <div>
      <h2>Users ({userStore.activeUsers.length} active)</h2>
      <ul>
        {userStore.users.map(user => (
          <UserItem key={user.id} user={user} />
        ))}
      </ul>
    </div>
  );
});

export default UserList;
```

### 3.2. Small, Focused `observer` Components

Break down your UI into many small `observer` components. This limits the re-render surface, allowing MobX to optimize updates efficiently.

```typescript
// src/components/UserItem.tsx
import React from 'react';
import { observer } from 'mobx-react-lite';

interface UserItemProps {
  user: { id: string; name: string; isActive: boolean };
}

const UserItem: React.FC<UserItemProps> = observer(({ user }) => {
  console.log(`Rendering UserItem: ${user.name}`); // Observe re-renders
  return (
    <li>
      {user.name} {user.isActive ? '(Active)' : ''}
    </li>
  );
});

export default UserItem;
```

### 3.3. De-reference Observables as Late as Possible

Access observable properties inside the `observer` component's render function (or JSX) rather than in intermediate variables outside of it. This allows MobX to track dependencies more precisely.

❌ **BAD: Early de-referencing**
```typescript
const UserProfile: React.FC = observer(({ user }) => {
  const userName = user.name; // De-referenced too early
  return <div>Name: {userName}</div>;
});
```

✅ **GOOD: Late de-referencing**
```typescript
const UserProfile: React.FC = observer(({ user }) => {
  return <div>Name: {user.name}</div>; // De-referenced inside JSX
});
```

### 3.4. Use Stable Keys for Lists

Always provide a stable, unique `key` prop for elements in lists. Never use array indexes as keys, as this leads to incorrect re-rendering behavior when items are added, removed, or reordered.

❌ **BAD: Array index as key**
```typescript
<ul>
  {userStore.users.map((user, index) => (
    <UserItem key={index} user={user} />
  ))}
</ul>
```

✅ **GOOD: Unique ID as key**
```typescript
<ul>
  {userStore.users.map(user => (
    <UserItem key={user.id} user={user} />
  ))}
</ul>
```

## 4. MobX-State-Tree (MST) Specifics

If using MobX-State-Tree, most MobX principles apply. However, there's one critical anti-pattern to avoid.

### 4.1. Never Spread MST Models as Props

Passing an MST model instance directly is crucial for MobX's tracking. Spreading an MST model (`{...model}`) breaks reactivity and should be strictly avoided.

❌ **BAD: Spreading an MST model**
```typescript
// Assuming 'user' is an MST model instance
<UserProfile {...user} />
```

✅ **GOOD: Passing an MST model instance directly**
```typescript
// Assuming 'user' is an MST model instance
<UserProfile user={user} />
```

## 5. Type Safety with TypeScript

Leverage TypeScript interfaces to define the shape of your stores and models. This catches errors at compile time and improves developer experience.

```typescript
// src/stores/AuthStore.ts
import { makeObservable, observable, action } from 'mobx';
import { RootStore } from './RootStore'; // Import RootStore for type safety

export interface IAuthStore {
  token: string | null;
  isAuthenticated: boolean;
  login(username: string, password: string): Promise<void>;
  logout(): void;
}

export class AuthStore implements IAuthStore {
  token: string | null = null;
  isAuthenticated: boolean = false;
  private rootStore: RootStore; // Reference to root store

  constructor(rootStore: RootStore) {
    makeObservable(this, {
      token: observable,
      isAuthenticated: observable,
      login: action,
      logout: action,
    });
    this.rootStore = rootStore;
  }

  async login(username: string, password: string) {
    // ... API call ...
    this.token = 'some-jwt-token';
    this.isAuthenticated = true;
    // Example of cross-store action
    // this.rootStore.userStore.fetchCurrentUser();
  }

  logout() {
    this.token = null;
    this.isAuthenticated = false;
  }
}
```

## 6. Testing

Unit test your stores independently and use React Testing Library for components, mocking the store context.

### 6.1. Store Unit Tests (Jest)

```typescript
// src/stores/UserStore.test.ts
import { UserStore } from './UserStore';

describe('UserStore', () => {
  let userStore: UserStore;

  beforeEach(() => {
    userStore = new UserStore();
  });

  it('should initialize with empty users and not loading', () => {
    expect(userStore.users).toEqual([]);
    expect(userStore.isLoading).toBe(false);
  });

  it('should add a user', () => {
    const newUser = { id: '2', name: 'Bob', isActive: false };
    userStore.addUser(newUser);
    expect(userStore.users).toEqual([newUser]);
  });

  it('should compute active users correctly', () => {
    userStore.addUser({ id: '1', name: 'Alice', isActive: true });
    userStore.addUser({ id: '2', name: 'Bob', isActive: false });
    expect(userStore.activeUsers).toEqual([{ id: '1', name: 'Alice', isActive: true }]);
  });

  it('should set isLoading during fetch and update users', async () => {
    const promise = userStore.fetchUsers();
    expect(userStore.isLoading).toBe(true);
    await promise;
    expect(userStore.isLoading).toBe(false);
    expect(userStore.users.length).toBeGreaterThan(0);
  });
});
```

### 6.2. Component Tests (React Testing Library)

Mock the `useStore` hook to provide controlled store instances for component tests.

```typescript
// src/components/UserList.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
import { useStore } from '../contexts/StoreContext';
import { UserStore } from '../stores/UserStore';
import { RootStore } from '../stores/RootStore';

// Mock the useStore hook
jest.mock('../contexts/StoreContext', () => ({
  useStore: jest.fn(),
}));

describe('UserList', () => {
  let mockUserStore: UserStore;
  let mockRootStore: RootStore;

  beforeEach(() => {
    mockUserStore = new UserStore();
    mockRootStore = new RootStore();
    mockRootStore.userStore = mockUserStore; // Inject the mock user store

    (useStore as jest.Mock).mockReturnValue(mockRootStore); // Return the mock root store
  });

  it('renders loading state initially', () => {
    mockUserStore.isLoading = true;
    render(<UserList />);
    expect(screen.getByText('Loading users...')).toBeInTheDocument();
  });

  it('renders users after loading', async () => {
    mockUserStore.isLoading = false;
    mockUserStore.users = [{ id: '1', name: 'Alice', isActive: true }];
    mockUserStore.fetchUsers = jest.fn(() => {
      mockUserStore.users = [{ id: '1', name: 'Alice', isActive: true }];
      return Promise.resolve();
    });

    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByText('Users (1 active)')).toBeInTheDocument();
      expect(screen.getByText('Alice (Active)')).toBeInTheDocument();
    });
  });
});
```

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

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

Все правила IDE по теме «Фронтенд и UI» →
react-mobx-cursorrules-prompt-file PatrickJS Набор инструкций для Cursor по разработке на React с MobX. Задаёт архитектуру сторов, соглашения по хукам и строгий режим MobX при генерации кода. ★ 41 028звёзд репо frontend-state-management aj-geddes Скилл для работы с состоянием фронтенда: подсказывает, как организовать Redux, MobX, Zustand или Context API в приложении. CX Codex +1 480установок react-specialist VoltAgent Community Субагент для разбора и доработки React-кода: производительности, состояния, архитектуры и тестов. Подходит, когда проект уже есть и нужны точечные изменения. 815установок react-zustand-cursorrules-prompt-file PatrickJS Инструкция для Cursor по работе со стейт-менеджером Zustand в React и TypeScript. Задаёт архитектуру сторов, защищает от лишних ререндеров и отсекает серверный кэш от клиентских данных. ★ 41 028звёзд репо frontend-patterns affaan-m Скилл подсказывает паттерны для React и Next.js: компоненты, состояние, загрузка данных и производительность. Нужен при разработке и ревью интерфейса. CX Codex +1 5 900установок zustand-state-management Mindrally Задаёт правила архитектуры сторов Zustand в React и TypeScript: от нарезки слайсов до SSR и тестов. Помогает избежать лишних ререндеров и ошибок гидратации. CX Codex +1 959установок

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

Весь набор →
react Правило для Cursor задаёт команде единые стандарты React: чистые компоненты, правила хуков, структура по фичам. Пригодится, когда в проекте несколько разработчиков и ИИ-агент пишет код вразнобой. ★ 3 580звёзд репо react-redux Направляет Cursor на использование Redux Toolkit, хуков и RTK Query в коде React вместо устаревшего connect и ручных редьюсеров. ★ 3 580звёзд репо react-query Правило задаёт структуру ключей кеша и рекомендует выносить запросы TanStack Query в пользовательские хуки. Пригодится для React-проектов на JavaScript или TypeScript. ★ 3 580звёзд репо react-native Правило задаёт обязательные практики для React Native: feature-first структуру, функциональные компоненты, хуки и типизацию. Пригодится, когда проект растёт и файлы расползаются. ★ 3 580звёзд репо railway Правило помогает настроить приложение для Railway: от структуры монорепозитория и секретов до проверок состояния и завершения работы сервиса. ★ 3 580звёзд репо qwik Правило Cursor, которое подсказывает агенту, как писать Qwik-приложения: component$, useStore, QRL с суффиксом $ и загрузка данных через loader$. ★ 3 580звёзд репо pytorch Задаёт стандарты чистого кода для PyTorch в Python-проектах. Направляет Cursor разделять архитектуру на модули, выносить тензоры на устройства и отключать градиенты при инференсе. ★ 3 580звёзд репо python Правило задаёт стиль и практики для Python-файлов в Cursor. Оно пригодится, когда нужно поддерживать единый формат, типизацию, структуру проекта и тесты. ★ 3 580звёзд репо

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

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