Practical useContext Examples

Introduction

The Context API in React is a powerful feature that allows developers to share state and logic across multiple components without prop drilling. Combined with the useContext hook, it provides a clean, efficient way to access global data in functional components.

The Context API is especially useful for scenarios like:

  • Theme switching (dark/light mode)
  • Authentication state management
  • Sharing user preferences or settings

In this article, we will cover practical examples of using useContext for real-world scenarios, demonstrating how it simplifies state management in React applications.


What is useContext?

The useContext hook allows functional components to consume a context created using React.createContext. It eliminates the need to pass props through intermediate components.

Basic Syntax

import React, { useContext } from "react";
import { MyContext } from "./MyContext";

function MyComponent() {
  const value = useContext(MyContext);
  return <div>{value}</div>;
}

Creating and Providing Context

To use useContext, you first need to create a context and provide a value using a Provider.

Example

import React, { createContext } from "react";

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const theme = "light";

  return (
&lt;ThemeContext.Provider value={theme}&gt;
  {children}
&lt;/ThemeContext.Provider&gt;
); }

Explanation

  • ThemeContext is created using createContext().
  • ThemeProvider wraps the component tree and provides a theme value.
  • Components consuming this context can access theme using useContext.

Example 1: Theme Switcher (Dark/Light Mode)

Step 1: Create Context

import React, { createContext, useState } from "react";

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
}; return (
&lt;ThemeContext.Provider value={{ theme, toggleTheme }}&gt;
  {children}
&lt;/ThemeContext.Provider&gt;
); }

Step 2: Consume Context in a Component

import React, { useContext } from "react";
import { ThemeContext } from "./ThemeContext";

function ThemeSwitcher() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
&lt;div style={{ background: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "#fff", padding: "20px" }}&gt;
  &lt;p&gt;Current theme: {theme}&lt;/p&gt;
  &lt;button onClick={toggleTheme}&gt;Toggle Theme&lt;/button&gt;
&lt;/div&gt;
); }

Step 3: Wrap App with Provider

import React from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "./ThemeContext";
import ThemeSwitcher from "./ThemeSwitcher";

ReactDOM.render(
  <ThemeProvider>
&lt;ThemeSwitcher /&gt;
</ThemeProvider>, document.getElementById("root") );

Explanation

  • ThemeContext provides the current theme and a toggle function.
  • ThemeSwitcher consumes the context using useContext.
  • The background and text color dynamically change based on the theme.

Example 2: Authentication State Across the App

Managing authentication state globally is one of the most common use cases for useContext.

Step 1: Create Auth Context

import React, { createContext, useState } from "react";

export const AuthContext = createContext();

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

  const login = (username) => setUser({ name: username });
  const logout = () => setUser(null);

  return (
&lt;AuthContext.Provider value={{ user, login, logout }}&gt;
  {children}
&lt;/AuthContext.Provider&gt;
); }

Step 2: Consume Auth Context in Components

Login Component

import React, { useContext, useState } from "react";
import { AuthContext } from "./AuthContext";

function Login() {
  const { login } = useContext(AuthContext);
  const [username, setUsername] = useState("");

  const handleLogin = () => login(username);

  return (
&lt;div&gt;
  &lt;input type="text" value={username} onChange={(e) =&gt; setUsername(e.target.value)} placeholder="Username" /&gt;
  &lt;button onClick={handleLogin}&gt;Login&lt;/button&gt;
&lt;/div&gt;
); }

Dashboard Component

import React, { useContext } from "react";
import { AuthContext } from "./AuthContext";

function Dashboard() {
  const { user, logout } = useContext(AuthContext);

  if (!user) return <p>Please log in</p>;

  return (
&lt;div&gt;
  &lt;p&gt;Welcome, {user.name}&lt;/p&gt;
  &lt;button onClick={logout}&gt;Logout&lt;/button&gt;
&lt;/div&gt;
); }

Step 3: Wrap App with AuthProvider

import React from "react";
import ReactDOM from "react-dom";
import { AuthProvider } from "./AuthContext";
import Login from "./Login";
import Dashboard from "./Dashboard";

function App() {
  return (
&lt;AuthProvider&gt;
  &lt;Login /&gt;
  &lt;Dashboard /&gt;
&lt;/AuthProvider&gt;
); } ReactDOM.render(<App />, document.getElementById("root"));

Explanation

  • AuthContext provides user, login, and logout.
  • Components access authentication state anywhere in the component tree without prop drilling.
  • This approach makes managing login state clean and scalable.

Example 3: Shared User Preferences

User preferences such as language, font size, or layout style can be shared globally using useContext.

Step 1: Create Preferences Context

import React, { createContext, useState } from "react";

export const PreferencesContext = createContext();

export function PreferencesProvider({ children }) {
  const [preferences, setPreferences] = useState({
language: "en",
fontSize: "medium"
}); const updatePreferences = (newPrefs) => {
setPreferences({ ...preferences, ...newPrefs });
}; return (
&lt;PreferencesContext.Provider value={{ preferences, updatePreferences }}&gt;
  {children}
&lt;/PreferencesContext.Provider&gt;
); }

Step 2: Consume Preferences in Components

Language Selector

import React, { useContext } from "react";
import { PreferencesContext } from "./PreferencesContext";

function LanguageSelector() {
  const { preferences, updatePreferences } = useContext(PreferencesContext);

  return (
&lt;div&gt;
  &lt;p&gt;Current Language: {preferences.language}&lt;/p&gt;
  &lt;button onClick={() =&gt; updatePreferences({ language: "en" })}&gt;English&lt;/button&gt;
  &lt;button onClick={() =&gt; updatePreferences({ language: "es" })}&gt;Spanish&lt;/button&gt;
&lt;/div&gt;
); }

Font Size Selector

import React, { useContext } from "react";
import { PreferencesContext } from "./PreferencesContext";

function FontSizeSelector() {
  const { preferences, updatePreferences } = useContext(PreferencesContext);

  return (
&lt;div&gt;
  &lt;p&gt;Font Size: {preferences.fontSize}&lt;/p&gt;
  &lt;button onClick={() =&gt; updatePreferences({ fontSize: "small" })}&gt;Small&lt;/button&gt;
  &lt;button onClick={() =&gt; updatePreferences({ fontSize: "medium" })}&gt;Medium&lt;/button&gt;
  &lt;button onClick={() =&gt; updatePreferences({ fontSize: "large" })}&gt;Large&lt;/button&gt;
&lt;/div&gt;
); }

Step 3: Wrap App with PreferencesProvider

import React from "react";
import ReactDOM from "react-dom";
import { PreferencesProvider } from "./PreferencesContext";
import LanguageSelector from "./LanguageSelector";
import FontSizeSelector from "./FontSizeSelector";

function App() {
  return (
&lt;PreferencesProvider&gt;
  &lt;LanguageSelector /&gt;
  &lt;FontSizeSelector /&gt;
&lt;/PreferencesProvider&gt;
); } ReactDOM.render(<App />, document.getElementById("root"));

Explanation

  • Preferences are stored in a single context.
  • Multiple components can read or update preferences without passing props.
  • This approach is scalable for adding new preference options in the future.

Combining Multiple Contexts

In larger apps, you may need to use multiple contexts (theme, auth, preferences) together.

Example

import React from "react";
import { ThemeProvider } from "./ThemeContext";
import { AuthProvider } from "./AuthContext";
import { PreferencesProvider } from "./PreferencesContext";
import Dashboard from "./Dashboard";

function App() {
  return (
&lt;ThemeProvider&gt;
  &lt;AuthProvider&gt;
    &lt;PreferencesProvider&gt;
      &lt;Dashboard /&gt;
    &lt;/PreferencesProvider&gt;
  &lt;/AuthProvider&gt;
&lt;/ThemeProvider&gt;
); }

Explanation

  • Multiple providers can be nested to provide separate contexts.
  • Each component can consume one or more contexts as needed using useContext.

Best Practices for useContext

  1. Use for Global Data Only
    • Do not overuse context for local state.
    • Local component state should still be managed with useState.
  2. Keep Context Small and Focused
    • Avoid storing unrelated data in a single context.
    • Create separate contexts for theme, auth, preferences, etc.
  3. Memoize Context Valuesconst value = useMemo(() => ({ preferences, updatePreferences }), [preferences]);
    • Prevents unnecessary re-renders of consuming components.
  4. Avoid Frequent Updates in Context
    • Avoid putting rapidly changing state (like animations) in context.
    • Use local state or other state management tools for high-frequency updates.
  5. Combine Contexts Carefully
    • Nest providers logically and avoid deep nesting when possible.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *