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
- 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
};
}
- 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>
);
}
- 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' })
};
}
- 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>
);
}
- 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>
);
}
Member discussion