Learn how to manage application state using React's Context API and custom hooks.
React's Context API provides a way to share state between components without prop drilling:
1import { createContext, useContext, useReducer, type Dispatch } from 'react'23interface Theme {4 dark: boolean5 color: string6}78type Action =9 | { type: 'TOGGLE_THEME' }10 | { type: 'SET_COLOR'; payload: string }1112interface State {13 theme: Theme14}1516interface ThemeContextType {17 state: State18 dispatch: Dispatch<Action>19}2021const initialState: State = {22 theme: {23 dark: false,24 color: 'blue',25 },26}2728const ThemeContext = createContext<ThemeContextType | undefined>(undefined)2930function themeReducer(state: State, action: Action): State {31 switch (action.type) {32 case 'TOGGLE_THEME':33 return {34 ...state,35 theme: {36 ...state.theme,37 dark: !state.theme.dark,38 },39 }40 case 'SET_COLOR':41 return {42 ...state,43 theme: {44 ...state.theme,45 color: action.payload,46 },47 }48 default:49 return state50 }51}
Create custom providers to manage specific state domains:
1'use client'23import { createContext, useReducer, useContext, type ReactNode } from 'react'45export function ThemeProvider({ children }: { children: ReactNode }) {6 const [state, dispatch] = useReducer(themeReducer, initialState)78 return (9 <ThemeContext.Provider value={{ state, dispatch }}>10 {children}11 </ThemeContext.Provider>12 )13}1415// Usage in layout16export default function RootLayout({17 children,18}: {19 children: React.ReactNode20}) {21 return (22 <html lang="en">23 <body>24 <ThemeProvider>25 {children}26 </ThemeProvider>27 </body>28 </html>29 )30}
Create custom hooks to access and modify context state:
1export function useTheme() {2 const context = useContext(ThemeContext)3 if (context === undefined) {4 throw new Error('useTheme must be used within a ThemeProvider')5 }6 return context7}89// Custom hook for specific theme actions10export function useThemeActions() {11 const { dispatch } = useTheme()1213 const toggleTheme = () => {14 dispatch({ type: 'TOGGLE_THEME' })15 }1617 const setColor = (color: string) => {18 dispatch({ type: 'SET_COLOR', payload: color })19 }2021 return {22 toggleTheme,23 setColor,24 }25}
Here's how to use the context and hooks in your components:
1'use client'23import { useTheme, useThemeActions } from '@/hooks/use-theme'45export function ThemeToggle() {6 const { state } = useTheme()7 const { toggleTheme, setColor } = useThemeActions()89 return (10 <div className="space-y-4">11 <button12 onClick={toggleTheme}13 className="rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium"14 >15 {state.theme.dark ? 'Switch to Light' : 'Switch to Dark'}16 </button>1718 <div className="flex gap-2">19 {['blue', 'green', 'red'].map((color) => (20 <button21 key={color}22 onClick={() => setColor(color)}23 className={`h-8 w-8 rounded-full ${24 state.theme.color === color ? 'ring-2 ring-offset-2' : ''25 }`}26 style={{ backgroundColor: color }}27 />28 ))}29 </div>30 </div>31 )32}3334// Using the theme values35export function ThemedComponent() {36 const { state } = useTheme()3738 return (39 <div40 className={`rounded-lg p-4 ${41 state.theme.dark ? 'bg-gray-900 text-white' : 'bg-white text-gray-900'42 }`}43 style={{ borderColor: state.theme.color }}44 >45 <h2>Themed Content</h2>46 <p>This component respects the current theme settings.</p>47 </div>48 )49}