2 min read

State Management Strategies in React

1. Component Composition and State Lifting

  • Break your application into smaller, focused components
  • Keep state as close as possible to where it's needed
  • Lift state up only when it needs to be shared

2. Context API

  • Group related states into contexts
  • Create multiple contexts to avoid unnecessary re-renders
  • Use context providers to make state available to component trees

3. State Management Libraries

  • Redux: Ideal for complex applications with many state interactions
  • MobX: Provides more automatic and reactive state management
  • Zustand: Simpler than Redux with hooks-based API
  • Jotai/Recoil: Atom-based state management for fine-grained control

4. Custom Hooks

  • Create reusable hooks for related state logic
  • Group related states into a single custom hook
  • Implement reducer pattern with useReducer for complex state

Some examples to demonstrate combining these approaches

  1. Custom Hook for User-related state
function useUserState() {
  const [user, setUser] = useState(null);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [preferences, setPreferences] = useState({});
  
  const login = useCallback((userData) => {
    setUser(userData);
    setIsAuthenticated(true);
  }, []);
  
  const logout = useCallback(() => {
    setUser(null);
    setIsAuthenticated(false);
  }, []);
  
  const updatePreferences = useCallback((newPrefs) => {
    setPreferences(prev => ({...prev, ...newPrefs}));
  }, []);
  
  return {
    user,
    isAuthenticated,
    preferences,
    login,
    logout,
    updatePreferences
  };
}
  1. Context API for theme state
const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [fontSize, setFontSize] = useState('medium');
  const [colorScheme, setColorScheme] = useState('default');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  const value = {
    theme,
    fontSize,
    coloeScheme,
    setFontSize,
    setColorScheme,
    toggleTheme
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
  1. useReducer for complex UI state
function uiReducer(state, action) {
  switch (action.type) {
    case 'OPEN_MODAL':
      return { ...state, activeModal: action.payload };
    case 'CLOSE_MODAL':
      return { ...state, activeModal: null };
    case 'SET_SIDEBAR_OPEN':
      return { ...state, isSidebarOpen: action.payload };
    case 'TOGGLE_NOTIFICATION_PANEL':
      return { ...state, isNotificationPanelOpen: !state.isNotificationPanelOpen };
    default:
      return state;
  }
}

function useUIState() {
  const initialState = {
    activeModal: null,
    isSidebarOpen: false,
    isNotificationPanelOpen: false
  };

  const [uiState, dispatch] = useReducer(uiReducer, initialState);

  return {
    uiState,
    openModal: (modalId) => dispatch({ type: 'OPEN_MODAL', payload: modalId }),
    closeModal: () => dispatch({ type: 'CLOSE_MODAL' }),
    setSidebarOpen: (isOpen) => dispatch({ type: 'SET_SIDEBAR_OPEN', payload: isOpen }),
    toggleNotificationPanel: () => dispatch({ type: 'TOGGLE_NOTIFICATION_PANEL' })
  };
}
  1. Main App Component
function App() {
  // Local form state
  const [formData, setFormData] = useState({});
  
  // User state from custom hook
  const userState = useUserState();
  
  // UI state from reducer
  const uiController = useUIState();
  
  // Additional component-specific states
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  
  return (
    <ThemeProvider>
      <UserContext.Provider value={userState}>
        <div className="app">
          <Header 
            user={userState.user}
            openNotifications={uiController.toggleNotificationPanel}
            toggleSidebar={() => uiController.setSidebarOpen(!uiController.uiState.isSidebarOpen)}
          />
          
          <Sidebar 
            isOpen={uiController.uiState.isSidebarOpen}
            onClose={() => uiController.setSidebarOpen(false)}
          />
          
          <main>
            {isLoading ? (
              <LoadingSpinner />
            ) : error ? (
              <ErrorMessage message={error} />
            ) : (
              <Content />
            )}
          </main>
          
          {uiController.uiState.activeModal && (
            <Modal 
              id={uiController.uiState.activeModal}
              onClose={uiController.closeModal}
            />
          )}
        </div>
      </UserContext.Provider>
    </ThemeProvider>
  );
}
  1. Example of a component that consumes multiple contexts
function UserProfileSettings() {
  // Access user state from context
  const { user, updatePreferences } = useContext(UserContext);
  
  // Access theme state from context
  const { theme, setFontSize } = useContext(ThemeContext);
  
  // Local component state
  const [isEditing, setIsEditing] = useState(false);
  const [localPreferences, setLocalPreferences] = useState({});
  
  // Update local state when user changes
  useEffect(() => {
    if (user?.preferences) {
      setLocalPreferences(user.preferences);
    }
  }, [user?.preferences]);
  
  const handleSave = () => {
    updatePreferences(localPreferences);
    setIsEditing(false);
  };
  
  return (
    <div className={`profile-settings ${theme}`}>
      <h2>User Settings</h2>
      {/* Settings form components */}
      <button onClick={() => setIsEditing(true)}>Edit</button>
      <button onClick={handleSave}>Save</button>
    </div>
  );
}