mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
## 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)
101 lines
3 KiB
Text
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
|