React – useContext

By | 08/04/2026

In this post, we will see what useContext is, when to use it, and how to share data between pages in a React application.
But first of all, what is useContext?
“React normally passes data from parent to child through props. This works fine for small apps, but as soon as we need the same data in multiple unrelated pages, we end up passing it through components that do not even need it. This problem is called prop drilling. The Context API solves it by creating a shared store that any component in the tree can read from directly and no props needed. Context is a general purpose tool: we can use it for a shopping cart, a theme, a selected language, user preferences, and much more.
Do not use it for data only one components needs. In that case, a simple prop or local state is cleaner.”

In order to see how useContext works, we will build a small app: a login page, a home page, and two inner pages (About and Info) that all display the logged-in username.
First, install React Router if we have not already, with the command:

npm install react-router-dom

[AuthContext.jsx]
This is the heart of the pattern. It creates the store, manages the state and exposes a clean hook for consuming it.

import { createContext, useContext, useState } from "react";
 
const AuthContext = createContext(null);
 
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
 
  // Called by LoginPage after credentials are validated.
  // Saves the username and marks the session as active.
  const login = (username) => {
    setUser({ username });
    setIsAuthenticated(true);
  };
 
  // Called by any page to end the session.
  const logout = () => {
    setUser(null);
    setIsAuthenticated(false);
  };
 
  return (
    <AuthContext.Provider value={{ user, isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}
 
// Custom hook — pages import this instead of the context object directly.
export function useAuth() {
  return useContext(AuthContext);
}


[LoginPage.jsx]
When the credentials are correct, we call login(username) that single call updates the shared store and makes the username available to every page in the app.

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../Context/AuthContext";
 
const VALID_USERS = [
  { username: "damiano",  password: "pass123" },
];
 

function LoginPage() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError]       = useState("");
  const [loading, setLoading]   = useState(false);

  const { login } = useAuth();
  const navigate  = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError("");
    setLoading(true);

    // Small delay to show the loading state
    await new Promise((r) => setTimeout(r, 600));

    const match = VALID_USERS.find(
      (u) => u.username === username && u.password === password
    );

    if (match) {
      login(username); // saves username in the shared AuthContext store
      navigate("/home");
    } else {
      setError("Invalid username or password.");
      setLoading(false);
    }
  };

  return (
    <div className="login-root">
      <div className="login-right">
        <div className="login-card">
          <h2>Welcome back</h2>
          <p className="subtitle">Sign in to continue to the demo</p>

          <form onSubmit={handleSubmit}>
            <div className="field">
              <label>Username</label>
              <input
                type="text"
                placeholder="e.g. marco"
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                autoComplete="username"
              />
            </div>

            <div className="field">
              <label>Password</label>
              <input
                type="password"
                placeholder="••••••••"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                autoComplete="current-password"
              />
            </div>

            {error && (
              <div className="error-msg">
                {error}
              </div>
            )}

            <button className="submit-btn" type="submit" disabled={loading}>
              {loading ? <><div className="spinner" /> Signing in…</> : "Sign In"}
            </button>
          </form>
        </div>
      </div>

    </div>
  );
}

export default LoginPage


[HomePage.jsx]
The home page reads user.username directly from AuthContext and no prop needed.

import { NavLink, Link } from "react-router-dom";
import { useAuth } from "../Context/AuthContext";

function HomePage() {
  const { user, logout } = useAuth();

  return (
    <div className="page">

      <nav className="navbar">
        <div className="navbar-brand">
          <div className="navbar-dot" />
          <span className="navbar-title">AuthContext Demo</span>
        </div>
        <div className="navbar-links">
          <NavLink to="/home"  className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>Home</NavLink>
          <NavLink to="/about" className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>About</NavLink>
          <NavLink to="/info"  className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>Info</NavLink>
          <button className="btn btn-danger" onClick={logout}>Log Out</button>
        </div>
      </nav>

      <div className="page-content">
        <div className="user-badge">
          <div className="user-badge-dot" />
          {user?.username}
        </div>

        <h1>Welcome back, <em>{user?.username}</em>!</h1>
        <p>You are logged in.</p>

        <hr className="divider" />

        <div className="card">
          <h3>Where do you want to go?</h3>
          <p style={{ marginBottom: '20px' }}>Pick a page to see AuthContext in action.</p>
          <div style={{ display: 'flex', gap: '12px' }}>
            <Link to="/about" className="btn btn-primary">About</Link>
            <Link to="/info"  className="btn btn-secondary">Info</Link>
          </div>
        </div>
      </div>

    </div>
  );
}

export default HomePage;


[AboutPage.jsx and InfoPage.jsx]
Both pages read from the same AuthContext.

import { NavLink, Link } from "react-router-dom";
import { useAuth } from "../Context/AuthContext";

function HomePage() {
  const { user, logout } = useAuth();

  return (
    <div className="page">

      <nav className="navbar">
        <div className="navbar-brand">
          <div className="navbar-dot" />
          <span className="navbar-title">AuthContext Demo</span>
        </div>
        <div className="navbar-links">
          <NavLink to="/home"  className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>Home</NavLink>
          <NavLink to="/about" className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>About</NavLink>
          <NavLink to="/info"  className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>Info</NavLink>
          <button className="btn btn-danger" onClick={logout}>Log Out</button>
        </div>
      </nav>

      <div className="page-content">
        <div className="user-badge">
          <div className="user-badge-dot" />
          {user?.username}
        </div>

        <h1>Welcome back, <em>{user?.username}</em>!</h1>
        <p>You are logged in</p>

        <hr className="divider" />

        <div className="card">
          <h3>Where do you want to go?</h3>
          <p style={{ marginBottom: '20px' }}>Pick a page to see AuthContext in action.</p>
          <div style={{ display: 'flex', gap: '12px' }}>
            <Link to="/about" className="btn btn-primary">About</Link>
            <Link to="/info"  className="btn btn-secondary">Info</Link>
          </div>
        </div>
      </div>

    </div>
  );
}

export default HomePage;

import { NavLink } from "react-router-dom";
import { useAuth } from "../Context/AuthContext";

function InfoPage() {
  const { user, isAuthenticated } = useAuth();

  return (
    <div className="page">

      <nav className="navbar">
        <div className="navbar-brand">
          <div className="navbar-dot" />
          <span className="navbar-title">AuthContext Demo</span>
        </div>
        <div className="navbar-links">
          <NavLink to="/home"  className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>Home</NavLink>
          <NavLink to="/about" className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>About</NavLink>
          <NavLink to="/info"  className={({ isActive }) => "nav-link" + (isActive ? " active" : "")}>Info</NavLink>
        </div>
      </nav>

      <div className="page-content">
        <div className="user-badge">
          <div className="user-badge-dot" />
          {user?.username}
        </div>

        <h1>Info</h1>
        <p>A snapshot of the current authentication state, read directly from AuthContext.</p>

        <hr className="divider" />

        <div className="card">
          <h3>Session details</h3>
          <p style={{ marginBottom: '12px' }}>Logged in as: <strong>{user?.username}</strong></p>
          <p>Status:
            {isAuthenticated
              ? <span className="alert alert-success" style={{ display: 'inline-flex', marginLeft: '10px', marginBottom: 0 }}> Authenticated</span>
              : <span className="alert alert-error"   style={{ display: 'inline-flex', marginLeft: '10px', marginBottom: 0 }}> Not authenticated</span>
            }
          </p>
        </div>
      </div>

    </div>
  );
}

export default InfoPage;


[App.jsx]
Wrap everything inside <AuthProvider> so that every route can access the shared store.

import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { AuthProvider } from "./Context/AuthContext";
import LoginPage  from "./components/LoginPage";
import HomePage   from "./components/HomePage";
import AboutPage  from "./components/AboutPage";
import InfoPage   from "./components/InfoPage";
 
function App() {
  return (
    // AuthProvider must wrap the router so every page can use useAuth()
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/"      element={<LoginPage />} />
          <Route path="/home"  element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
          <Route path="/info"  element={<InfoPage />} />
          <Route path="*"      element={<Navigate to="/" />} />
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
}

export default App


[Index.css]

@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:wght@300;400;500&display=swap');

/* ══════════════════════════════════════
   RESET & BASE
══════════════════════════════════════ */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'DM Sans', sans-serif;
  background: #f8fafc;
  color: #0f172a;
  min-height: 100vh;
}

/* ══════════════════════════════════════
   TYPOGRAPHY
══════════════════════════════════════ */
h1, h2, h3 {
  font-family: 'DM Serif Display', serif;
  color: #0f172a;
  line-height: 1.2;
}

h1 { font-size: clamp(28px, 4vw, 42px); margin-bottom: 12px; }
h2 { font-size: clamp(22px, 3vw, 32px); margin-bottom: 10px; }
h3 { font-size: 18px; margin-bottom: 8px; }

p  {
  font-size: 15px;
  color: #475569;
  line-height: 1.7;
  font-weight: 300;
}

strong { font-weight: 500; color: #0f172a; }

em {
  font-style: italic;
  color: #60a5fa;
}

/* ══════════════════════════════════════
   LAYOUT — PAGE WRAPPER
══════════════════════════════════════ */
.page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.page-content {
  flex: 1;
  max-width: 800px;
  width: 100%;
  margin: 0 auto;
  padding: 48px 24px;
  animation: fadeUp 0.4s ease both;
}

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ══════════════════════════════════════
   NAVBAR
══════════════════════════════════════ */
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 32px;
  background: #0f172a;
  border-bottom: 1px solid #1e293b;
}

.navbar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}

.navbar-dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #3b82f6;
  box-shadow: 0 0 10px rgba(59,130,246,0.7);
}

.navbar-title {
  font-family: 'DM Serif Display', serif;
  font-size: 17px;
  color: #e2e8f0;
  letter-spacing: 0.02em;
}

.navbar-links {
  display: flex;
  align-items: center;
  gap: 8px;
}

.nav-link {
  padding: 7px 16px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 400;
  color: #94a3b8;
  text-decoration: none;
  transition: background 0.2s, color 0.2s;
}

.nav-link:hover {
  background: #1e293b;
  color: #f1f5f9;
}

.nav-link.active {
  background: #1e3a5f;
  color: #93c5fd;
}

/* ══════════════════════════════════════
   CARD
══════════════════════════════════════ */
.card {
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 14px;
  padding: 32px;
  margin-bottom: 20px;
  box-shadow: 0 1px 6px rgba(0,0,0,0.04);
}

/* ══════════════════════════════════════
   USER BADGE  (shows the logged-in user)
══════════════════════════════════════ */
.user-badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  border-radius: 99px;
  font-size: 14px;
  color: #1d4ed8;
  font-weight: 500;
  margin-bottom: 24px;
}

.user-badge-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #3b82f6;
}

/* ══════════════════════════════════════
   BUTTONS
══════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 22px;
  border: none;
  border-radius: 10px;
  font-size: 14px;
  font-family: 'DM Sans', sans-serif;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.2s, transform 0.15s, box-shadow 0.2s;
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn-primary {
  background: #1e3a5f;
  color: #fff;
}

.btn-primary:hover {
  background: #2563eb;
  box-shadow: 0 4px 20px rgba(37,99,235,0.3);
}

.btn-secondary {
  background: #f1f5f9;
  color: #334155;
}

.btn-secondary:hover {
  background: #e2e8f0;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}

.btn-danger {
  background: #fef2f2;
  color: #dc2626;
}

.btn-danger:hover {
  background: #fee2e2;
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

/* ══════════════════════════════════════
   FORMS
══════════════════════════════════════ */
.field {
  margin-bottom: 20px;
}

.field label {
  display: block;
  font-size: 12px;
  font-weight: 500;
  color: #475569;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 8px;
}

.field input {
  width: 100%;
  padding: 13px 16px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  font-size: 15px;
  font-family: 'DM Sans', sans-serif;
  color: #0f172a;
  background: #fff;
  outline: none;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.field input::placeholder { color: #cbd5e1; }

.field input:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59,130,246,0.12);
}

/* ══════════════════════════════════════
   ALERTS & MESSAGES
══════════════════════════════════════ */
.alert {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border-radius: 8px;
  font-size: 13px;
  margin-bottom: 20px;
}

.alert-error {
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #dc2626;
  animation: shake 0.35s ease;
}

.alert-success {
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  color: #16a34a;
}

.alert-info {
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  color: #1d4ed8;
}

@keyframes shake {
  0%,100% { transform: translateX(0); }
  25%      { transform: translateX(-6px); }
  75%      { transform: translateX(6px); }
}

/* ══════════════════════════════════════
   SPINNER
══════════════════════════════════════ */
.spinner {
  width: 16px; height: 16px;
  border: 2px solid rgba(255,255,255,0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ══════════════════════════════════════
   DIVIDER
══════════════════════════════════════ */
.divider {
  border: none;
  border-top: 1px solid #e2e8f0;
  margin: 28px 0;
}

/* ══════════════════════════════════════
   HINT BOX
══════════════════════════════════════ */
.hint {
  padding: 14px;
  background: #f1f5f9;
  border-radius: 8px;
  font-size: 12px;
  color: #64748b;
  line-height: 1.6;
}

.hint strong { color: #334155; }

/* ══════════════════════════════════════
   LOGIN PAGE  (specific layout)
══════════════════════════════════════ */
.login-root {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr;
  background: #0b0f1a;
}

.login-left {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 48px;
  background: linear-gradient(145deg, #0f1e3d 0%, #0b0f1a 60%, #0d1a2e 100%);
  overflow: hidden;
}

.login-left::before {
  content: '';
  position: absolute;
  top: -120px; left: -120px;
  width: 500px; height: 500px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(59,130,246,0.18) 0%, transparent 70%);
  pointer-events: none;
}

.login-left::after {
  content: '';
  position: absolute;
  bottom: -80px; right: -80px;
  width: 360px; height: 360px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(99,102,241,0.14) 0%, transparent 70%);
  pointer-events: none;
}

.login-left .brand { display: flex; align-items: center; gap: 10px; z-index: 1; }
.login-left .brand-dot { width: 10px; height: 10px; border-radius: 50%; background: #3b82f6; box-shadow: 0 0 12px rgba(59,130,246,0.7); }
.login-left .brand-name { font-family: 'DM Serif Display', serif; font-size: 18px; color: #e2e8f0; }

.left-headline { z-index: 1; }
.left-headline h1 { font-family: 'DM Serif Display', serif; font-size: clamp(36px, 4vw, 52px); color: #f1f5f9; margin-bottom: 20px; }
.left-headline p  { font-size: 15px; color: #94a3b8; line-height: 1.7; max-width: 340px; font-weight: 300; }
.left-footer { z-index: 1; font-size: 12px; color: #475569; }

.login-right {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 40px;
  background: #f8fafc;
}

.login-card {
  width: 100%;
  max-width: 400px;
  animation: fadeUp 0.5s ease both;
}

.login-card h2    { font-family: 'DM Serif Display', serif; font-size: 30px; color: #0f172a; margin-bottom: 6px; }
.login-card .subtitle { font-size: 14px; color: #64748b; margin-bottom: 36px; font-weight: 300; }

/* ══════════════════════════════════════
   RESPONSIVE
══════════════════════════════════════ */
@media (max-width: 700px) {
  .login-root { grid-template-columns: 1fr; }
  .login-left  { display: none; }
  .login-right { background: #0b0f1a; }
  .login-card h2       { color: #f1f5f9; }
  .login-card .subtitle { color: #94a3b8; }
  .field label  { color: #94a3b8; }
  .field input  { background: #1e293b; border-color: #334155; color: #f1f5f9; }
  .field input::placeholder { color: #475569; }
  .hint { background: #1e293b; color: #94a3b8; }
  .hint strong { color: #cbd5e1; }

  .navbar { padding: 14px 16px; }
  .page-content { padding: 24px 16px; }
}


Now that we have written all the code, it is time to test it.
Run the application with

npm run dev

and open the browser.
We should see the login page.
Try entering wrong credentials first and the error message should appear.
Then log in with damiano / pass123 and we will be redirected to the home page.
From there, navigate to the About and Info pages and notice that our username appears on every page.


LOGOUT




Leave a Reply

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