Skip to content

Latest commit

 

History

History
219 lines (191 loc) · 5.2 KB

react-state-management-guidelines.md

File metadata and controls

219 lines (191 loc) · 5.2 KB

State Management Guidelines

Core State Management Principles

Twenty uses a combination of Recoil for global state and Apollo Client for server state management. This document outlines our state management conventions and best practices.

Global State Management

Recoil Usage

  • Use Recoil for global application state
  • Keep atoms small and focused
    // ✅ Correct
    // states/theme.ts
    export const themeState = atom<'light' | 'dark'>({
      key: 'themeState',
      default: 'light',
    });
    
    // states/user.ts
    export const userState = atom<User | null>({
      key: 'userState',
      default: null,
    });
    
    // ❌ Incorrect
    // states/globalState.ts
    export const globalState = atom({
      key: 'globalState',
      default: {
        theme: 'light',
        user: null,
        settings: {},
        // ... many other unrelated pieces of state
      },
    });

Atom Organization

  • Place atoms in the states/ directory
  • Group related atoms in feature-specific files
    // states/workspace/atoms.ts
    export const workspaceIdState = atom<string>({
      key: 'workspaceIdState',
      default: '',
    });
    
    export const workspaceSettingsState = atom<WorkspaceSettings>({
      key: 'workspaceSettingsState',
      default: defaultSettings,
    });

Server State Management

Apollo Client Usage

  • Use Apollo Client for all GraphQL operations
  • Leverage Apollo's caching capabilities
    // ✅ Correct
    const { data, loading } = useQuery(GET_USER_QUERY, {
      variables: { id },
      fetchPolicy: 'cache-first',
    });
    
    // ❌ Incorrect
    const [user, setUser] = useState(null);
    useEffect(() => {
      fetch('/api/user/' + id).then(setUser);
    }, [id]);

Query Organization

  • Separate operation files
  • Use fragments for shared fields
    // queries/user.ts
    export const UserFragment = gql`
      fragment UserFields on User {
        id
        name
        email
      }
    `;
    
    export const GET_USER = gql`
      query GetUser($id: ID!) {
        user(id: $id) {
          ...UserFields
        }
      }
      ${UserFragment}
    `;

State Management Best Practices

Multiple Small Atoms

  • Prefer multiple small atoms over prop drilling
  • Keep atoms focused on specific features
    // ✅ Correct
    export const selectedViewState = atom<string>({
      key: 'selectedViewState',
      default: '',
    });
    
    export const viewFiltersState = atom<ViewFilters>({
      key: 'viewFiltersState',
      default: {},
    });
    
    // ❌ Incorrect - Prop drilling
    const ViewContainer = ({ selectedView, filters, onViewChange }) => {
      return (
        <ViewHeader view={selectedView} onViewChange={onViewChange}>
          <ViewContent>
            <ViewFilters filters={filters} />
          </ViewContent>
        </ViewHeader>
      );
    };

No useRef for State

  • Never use useRef for state management
  • Use proper state management tools
    // ✅ Correct
    const [count, setCount] = useState(0);
    // or
    const [count, setCount] = useRecoilState(countState);
    
    // ❌ Incorrect
    const countRef = useRef(0);

Data Fetching

  • Extract data fetching to sibling components
  • Keep components focused on presentation
    // ✅ Correct
    const UserProfileContainer = () => {
      const { data, loading } = useQuery(GET_USER);
      if (loading) return <LoadingSpinner />;
      return <UserProfile user={data.user} />;
    };
    
    const UserProfile = ({ user }: UserProfileProps) => {
      return <div>{user.name}</div>;
    };
    
    // ❌ Incorrect
    const UserProfile = () => {
      const { data, loading } = useQuery(GET_USER);
      if (loading) return <LoadingSpinner />;
      return <div>{data.user.name}</div>;
    };

Hook Usage

  • Use appropriate hooks for state access
  • Choose between useRecoilValue and useRecoilState based on needs
    // ✅ Correct - Read-only access
    const theme = useRecoilValue(themeState);
    
    // ✅ Correct - Read-write access
    const [theme, setTheme] = useRecoilState(themeState);
    
    // ❌ Incorrect - Using state setter when only reading
    const [theme, _] = useRecoilState(themeState);

Performance Considerations

Selector Usage

  • Use selectors for derived state
  • Memoize complex calculations
    // ✅ Correct
    const filteredUsersState = selector({
      key: 'filteredUsersState',
      get: ({ get }) => {
        const users = get(usersState);
        const filter = get(userFilterState);
        return users.filter(user => 
          user.name.toLowerCase().includes(filter.toLowerCase())
        );
      },
    });
    
    // ❌ Incorrect - Calculating in component
    const UserList = () => {
      const users = useRecoilValue(usersState);
      const filter = useRecoilValue(userFilterState);
      const filteredUsers = users.filter(user =>
        user.name.toLowerCase().includes(filter.toLowerCase())
      );
      return <List users={filteredUsers} />;
    };

Cache Management

  • Configure appropriate cache policies
  • Handle cache invalidation properly
    // ✅ Correct
    const [updateUser] = useMutation(UPDATE_USER, {
      update: (cache, { data }) => {
        cache.modify({
          id: cache.identify(data.updateUser),
          fields: {
            name: () => data.updateUser.name,
          },
        });
      },
    });