React Mastery / Advanced Concepts

Context+Reducer - Using with a Custom Hook

🧠 Why Use a Custom Hook?

Without a custom hook:

  • Every component imports useContext

  • Context logic leaks into UI

  • Refactoring becomes painful

With a custom hook:

  • One clean API

  • Centralized logic

  • Better DX and testability

Rule of thumb:

πŸ‘‰ Never use useContext directly in components.


πŸͺœ Step 1: Keep Context & Provider as-is

We already have:

  • AuthContext

  • AuthProvider

  • Async logic inside provider

No changes needed here βœ…


πŸͺœ Step 2: Create the Custom Hook

πŸ” Why this step?

This hook:

  • Reads from AuthContext

  • Adds safety checks

  • Exposes a clean interface

πŸ“ useAuth.js

import { useContext } from 'react';
import { AuthContext } from './AuthContext';

export function useAuth() {
  const context = useContext(AuthContext);

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

  return context;
}

πŸ“Œ What this gives you:

  • No repeated useContext(AuthContext)

  • Early error detection

  • Cleaner components


πŸͺœ Step 3: Refactor the Provider Value

πŸ” Why?

Expose only what consumers need, not the entire internals.

πŸ“ AuthProvider.jsx

return (
  <AuthContext.Provider
    value={{
      user: state.user,
      isAuthenticated: state.isAuthenticated,
      loading: state.loading,
      error: state.error,
      login,
      logout,
    }}
  >
    {children}
  </AuthContext.Provider>
);

πŸ“Œ Benefits:

  • Clear public API

  • State shape can change internally

  • Components remain unaffected


πŸͺœ Step 4: Use the Custom Hook in Components

πŸ” Why this is powerful?

Components:

  • Don’t know about Context

  • Don’t dispatch actions

  • Only call meaningful functions

πŸ“ Dashboard.jsx

import { useAuth } from './useAuth';

export default function Dashboard() {
  const {
    user,
    isAuthenticated,
    loading,
    error,
    login,
    logout,
  } = useAuth();

  if (loading) return <p>Logging in...</p>;
  if (error) return <p>{error}</p>;

  return (
    <>
      {isAuthenticated ? (
        <>
          <h2>Welcome, {user.name}</h2>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={login}>Login</button>
      )}
    </>
  );
}

✨ UI is now:

  • Clean

  • Declarative

  • Business-logic free


🧠 Final Architecture (Best Practice)


πŸ§ͺ Folder Structure (Recommended)

auth/
 β”œβ”€β”€ AuthContext.js
 β”œβ”€β”€ authReducer.js
 β”œβ”€β”€ AuthProvider.jsx
 └── useAuth.js

This scales really well.