Skip to main content

React Context API Usage

Answer

Context provides a way to pass data through the component tree without manually passing props at every level. It's useful for "global" data like themes, user authentication, or locale.

Context Flow

Creating and Using Context

// 1. Create Context
const ThemeContext = React.createContext("light"); // Default value

// 2. Provide Context
function App() {
const [theme, setTheme] = useState("dark");

return (
<ThemeContext.Provider value={theme}>
<Header />
<Main />
<Footer />
</ThemeContext.Provider>
);
}

// 3. Consume Context
function Header() {
const theme = useContext(ThemeContext);

return <header className={theme}>Current theme: {theme}</header>;
}

Context with Multiple Values

// Create context with object value
const UserContext = React.createContext(null);

function UserProvider({ children }) {
const [user, setUser] = useState(null);

const login = async (credentials) => {
const userData = await authService.login(credentials);
setUser(userData);
};

const logout = () => {
setUser(null);
};

const value = {
user,
login,
logout,
isAuthenticated: !!user,
};

return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}

// Custom hook for consuming
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error("useUser must be used within UserProvider");
}
return context;
}

// Usage
function Profile() {
const { user, logout } = useUser();

return (
<div>
<h1>Welcome, {user.name}</h1>
<button onClick={logout}>Logout</button>
</div>
);
}

Multiple Contexts

function App() {
return (
<ThemeProvider>
<UserProvider>
<NotificationProvider>
<MainApp />
</NotificationProvider>
</UserProvider>
</ThemeProvider>
);
}

// Consume multiple contexts
function Header() {
const theme = useTheme();
const { user } = useUser();
const { notify } = useNotification();

return <header className={theme}>Welcome, {user?.name}</header>;
}

Performance Optimization

// ❌ Problem: All consumers re-render when any value changes
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [settings, setSettings] = useState({});

// New object every render!
return (
<UserContext.Provider value={{ user, settings, setUser, setSettings }}>
{children}
</UserContext.Provider>
);
}

// ✅ Solution 1: Split contexts
const UserContext = React.createContext(null);
const SettingsContext = React.createContext(null);

// ✅ Solution 2: Memoize value
function UserProvider({ children }) {
const [user, setUser] = useState(null);

const value = useMemo(() => ({ user, setUser }), [user]);

return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}

// ✅ Solution 3: Separate state and dispatch contexts
const StateContext = React.createContext(null);
const DispatchContext = React.createContext(null);

function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}

Context vs Props vs State Management

Use CaseSolution
Parent-child dataProps
2-3 levels deepProps (prop drilling ok)
Theme, auth, i18nContext
Complex app stateRedux/Zustand + Context
Server stateReact Query/SWR

Common Pattern: Provider Composition

// Compose providers cleanly
function AppProviders({ children }) {
return (
<ErrorBoundary>
<ThemeProvider>
<AuthProvider>
<NotificationProvider>{children}</NotificationProvider>
</AuthProvider>
</ThemeProvider>
</ErrorBoundary>
);
}

function App() {
return (
<AppProviders>
<Router>
<Routes />
</Router>
</AppProviders>
);
}

Key Points

  • Context avoids prop drilling
  • Use createContext, Provider, and useContext
  • Create custom hooks for context consumption
  • Split contexts to avoid unnecessary re-renders
  • Memoize context values to prevent re-renders
  • Context is not a replacement for all state management
  • Best for infrequently changing data (theme, auth)