Unlocking the Full Potential of React Context with Custom Hooks

Love Trivedi
4 min readOct 30, 2024

--

React’s useContext hook has become a popular choice for managing state across applications. While useContext on its own is useful, combining it with custom hooks can take your state management to the next level. This approach helps in organizing code, creating reusable logic, and simplifying complex state interactions.

In this guide, we’ll walk through creating custom hooks that leverage useContext to efficiently manage and share state. Let’s dive in!

Why Combine useContext with Custom Hooks?

By itself, useContext provides an easy way to consume context, but by pairing it with a custom hook, you can:

  • Encapsulate logic: Keep complex logic within a hook, making the component cleaner.
  • Reusability: Create hooks that can be reused across components with minimal setup.
  • Simplicity: Reduce boilerplate code, improving readability and maintenance.

Step 1: Setting Up Context

Let’s start with a basic example. Suppose we’re building an application with a theme toggle functionality (light mode and dark mode). We’ll start by creating a ThemeContext and a ThemeProvider.

Create the Theme Context

// src/context/ThemeContext.js
import { createContext, useState } from 'react';

export const ThemeContext = createContext();

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

const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};

This setup provides theme and toggleTheme to any component wrapped with ThemeProvider.

Step 2: Creating a Custom Hook for Theme Context

To avoid repeatedly importing and using useContext(ThemeContext) in components, we’ll create a custom hook called useTheme. This hook will simplify consuming ThemeContext.

Create the useTheme Hook

// src/hooks/useTheme.js
import { useContext } from 'react';
import { ThemeContext } from '../context/ThemeContext';

const useTheme = () => {
const context = useContext(ThemeContext);

if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}

return context;
};

export default useTheme;

The custom hook useTheme allows any component to access theme and toggleTheme directly without additional setup.

Step 3: Using the Custom Hook in Components

Now we can use useTheme in any component to access and manage the theme state:

Creating a Component with Theme Toggle

// src/components/ThemeToggle.js
import useTheme from '../hooks/useTheme';

const ThemeToggle = () => {
const { theme, toggleTheme } = useTheme();

return (
<div style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
<p>Current Theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};

export default ThemeToggle;

This component’s background color and text change according to the theme state, and clicking the button toggles the theme.

Step 4: Reusing the Hook Across Components

One advantage of using custom hooks is that you can reuse useTheme in any other component. Here’s an example:

Display Theme in Another Component

// src/components/ThemeInfo.js
import useTheme from '../hooks/useTheme';

const ThemeInfo = () => {
const { theme } = useTheme();
return <p>The current theme is: {theme}</p>;
};

export default ThemeInfo;

Simply import useTheme and retrieve the theme state without needing additional boilerplate or context setup.

Step 5: Extending with More Contexts

Let’s say you want to add user authentication in the same app. We can use the same approach:

  1. Create the Auth Context
// src/context/AuthContext.js
import { createContext, useState } from 'react';

export const AuthContext = createContext();

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

const login = (username) => {
setUser({ username });
};

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

return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};

2. Create a useAuth Custom Hook

// src/hooks/useAuth.js
import { useContext } from 'react';
import { AuthContext } from '../context/AuthContext';

const useAuth = () => {
const context = useContext(AuthContext);

if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}

return context;
};

export default useAuth;

3. Using the useAuth Hook

// src/components/UserProfile.js
import useAuth from '../hooks/useAuth';

const UserProfile = () => {
const { user, login, logout } = useAuth();

return (
<div>
{user ? (
<div>
<p>Welcome, {user.username}!</p>
<button onClick={logout}>Logout</button>
</div>
) : (
<button onClick={() => login('JohnDoe')}>Login as John Doe</button>
)}
</div>
);
};

export default UserProfile;

This structure keeps each context self-contained and accessible through custom hooks, making it easy to expand and manage independently.

Best Practices for Using Custom Hooks with Context

  1. Use Custom Hooks to Wrap Context: Encapsulating useContext calls within hooks ensures clean, easy-to-read components.
  2. Throw Errors Outside Provider: As seen in our custom hooks, adding an error when the hook is used outside its provider helps prevent bugs.
  3. Modularize Contexts: When building larger applications, split contexts based on feature domains (e.g., theme, user, settings) for flexibility and scalability.
  4. Optimize Re-renders: Use React.memo and useMemo to optimize components that frequently read from context, especially in larger apps.

Conclusion

By combining useContext with custom hooks, we unlock React’s full potential for modular, reusable state management. This approach keeps components focused on their UI logic, leaving state handling to custom hooks, resulting in cleaner, more maintainable code.

As applications grow, custom hooks provide an organized structure that enhances productivity and ease of understanding. Start implementing custom hooks today, and watch your React code become simpler, cleaner, and more efficient!

--

--

Love Trivedi

Full Stack Developer | Problem Solver | Knowledge Share, 🚀 Expertise: JavaScript enthusiast specializing in ReactJS, Angular, and Node.js.