121788c42f
## Summary Removes the `recoil` dependency entirely from `package.json` and `twenty-front/package.json`, completing the migration to Jotai as the sole state management library. Removes all Recoil infrastructure: `RecoilRoot` wrapper from `App.tsx` and test decorators, `RecoilDebugObserver`, Recoil-specific ESLint rules (`use-getLoadable-and-getValue-to-get-atoms`, `useRecoilCallback-has-dependency-array`), and legacy Recoil utility hooks/types (`useRecoilComponentState`, `useRecoilComponentValue`, `createComponentState`, `createFamilyState`, `getSnapshotValue`, `cookieStorageEffect`, `localStorageEffect`, etc.). Renames all `V2`-suffixed Jotai state files and types to their canonical names (e.g., `ComponentStateV2` -> `ComponentState`, `agentChatInputStateV2` -> `agentChatInputState`, `SelectorCallbacksV2` -> `SelectorCallbacks`), and removes the now-redundant V1 counterparts. Updates ~433 files across the codebase to use the renamed Jotai imports, remove Recoil imports, and clean up test wrappers (`RecoilRootDecorator` -> `JotaiRootDecorator`).
102 lines
3.1 KiB
Plaintext
102 lines
3.1 KiB
Plaintext
---
|
|
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 (like useRecoilState)
|
|
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
|
|
|
// useAtomStateValue - read only (like useRecoilValue)
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
|
|
// useSetAtomState - write only (like useSetRecoilState)
|
|
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
|
```
|
|
|
|
## Provider
|
|
Jotai works without a Provider by default (unlike Recoil's RecoilRoot). 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
|