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)