twenty/.cursor/rules/react-state-management.mdc
Charles Bochet 2674589b44
Remove any recoil reference from project (#18250)
## Remove all Recoil references and replace with Jotai

### Summary

- Removed every occurrence of Recoil from the entire codebase, replacing
with Jotai equivalents where applicable
- Updated `README.md` tech stack: `Recoil` → `Jotai`
- Rewrote documentation code examples to use
`createAtomState`/`useAtomState` instead of `atom`/`useRecoilState`, and
removed `RecoilRoot` wrappers
- Cleaned up source code comment and Cursor rules that referenced Recoil
- Applied changes across all 13 locale translations (ar, cs, de, es, fr,
it, ja, ko, pt, ro, ru, tr, zh)
2026-02-26 10:28:40 +01:00

101 lines
3 KiB
Text

---
description: React state management guidelines for Twenty CRM
alwaysApply: false
---
# React State Management
## Jotai Patterns
```typescript
// ✅ Atoms for primitive state (use createAtomState for keyed state with optional persistence)
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentUserState = createAtomState<User | null>({
key: 'currentUserState',
defaultValue: null,
});
// ✅ Derived atoms for computed state (use createAtomSelector)
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
export const userDisplayNameSelector = createAtomSelector({
key: 'userDisplayNameSelector',
get: ({ get }) => {
const user = get(currentUserState);
return user ? `${user.firstName} ${user.lastName}` : 'Guest';
},
});
// ✅ Atom factory pattern for dynamic atoms (use createAtomFamilyState)
import { createAtomFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomFamilyState';
export const userByIdState = createAtomFamilyState<User | null, string>({
key: 'userByIdState',
defaultValue: null,
});
```
## Jotai Hooks
```typescript
// useAtomState - read and write
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
// useAtomStateValue - read only
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
// useSetAtomState - write only
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
```
## Provider
Jotai works without a Provider by default. For scoped stores or testing, use `Provider` from `jotai`.
## Local State Guidelines
```typescript
// ✅ Multiple useState for unrelated state
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<User[]>([]);
// ✅ useReducer for complex state logic
type FormAction =
| { type: 'SET_FIELD'; field: string; value: string }
| { type: 'SET_ERRORS'; errors: Record<string, string> }
| { type: 'RESET' };
const formReducer = (state: FormState, action: FormAction): FormState => {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value };
case 'SET_ERRORS':
return { ...state, errors: action.errors };
case 'RESET':
return initialFormState;
default:
return state;
}
};
```
## Data Flow Rules
- **Props down, events up** - Unidirectional data flow
- **Avoid bidirectional binding** - Use callback functions
- **Normalize complex data** - Use lookup tables over nested objects
```typescript
// ✅ Normalized state structure
type UsersState = {
byId: Record<string, User>;
allIds: string[];
};
// ✅ Functional state updates
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
```
## Performance Tips
- Use atom factory pattern (createAtomFamilyState) for dynamic data collections
- Derived atoms (createAtomSelector) are automatically memoized by Jotai
- Avoid heavy computations in derived atoms
- Batch state updates when possible