DEV Community

Mohamed Idris
Mohamed Idris

Posted on

How to share state in React using Context API

Imagine this scenario: in our app we have the following component structure:

  • Navbar component

    • holds the user state and a logout function (setUser(null))
    • inside Navbar, we have NavLinks
    • inside NavLinks, we have UserContainer

Now, we want to pass user and logout to UserContainer.

If we pass them as props from Navbar → NavLinks → UserContainer, this becomes prop drilling. Prop drilling isn’t very clean, because we’re passing props through components that don’t actually need them, just to reach the target component.

This is where Context API, Redux, and other state-management libraries help.

Below is a simple example of how we can use Context API for this case.


Navbar

import { createContext, useState } from 'react';
import NavLinks from './NavLinks';

export const NavbarContext = createContext();

const Navbar = () => {
  const [user, setUser] = useState({ name: 'Yu' });
  const logout = () => setUser(null);

  return (
    <NavbarContext.Provider value={{ user, logout }}>
      <div className="navbar">
        <h5>ContextAPI</h5>
        <NavLinks />
      </div>
    </NavbarContext.Provider>
  );
};

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

NavLinks

This component simply renders its content, including UserContainer.


UserContainer

import { useContext } from 'react';
import { NavbarContext } from './Navbar';

const UserContainer = () => {
  // const value = useContext(NavbarContext);
  const { user, logout } = useContext(NavbarContext);

  return (
    <div className="user-container">
      {user?.name ? (
        <>
          <p>{`Hello, ${user.name.toUpperCase()}!`}</p>
          <button className="btn" onClick={logout}>
            Logout
          </button>
        </>
      ) : (
        <p>Please login</p>
      )}
    </div>
  );
};

export default UserContainer;
Enter fullscreen mode Exit fullscreen mode

Summary

There are just a few steps needed:

1- Create a context in the parent component

   export const NavbarContext = createContext();
Enter fullscreen mode Exit fullscreen mode

2- Wrap the component tree with NavbarContext.Provider and pass the shared data via value
This makes the data accessible to all nested components.

3- Consume the context in any child component

   const { user, logout } = useContext(NavbarContext);
Enter fullscreen mode Exit fullscreen mode

And that’s it — no prop drilling, cleaner code, and easier state sharing across components.

Credits: John Smilga’s course

Top comments (2)

Collapse
 
edriso profile image
Mohamed Idris

💡 Extra tip: We can also wrap the Context logic inside a custom hook to make it cleaner and easier to reuse.

Instead of calling useContext(NavbarContext) everywhere, we create a custom hook and use that instead.

Navbar:

import { createContext, useState, useContext } from 'react';
import NavLinks from './NavLinks';

const NavbarContext = createContext();

// Custom hook
export const useAppContext = () => useContext(NavbarContext);

const Navbar = () => {
  const [user, setUser] = useState({ name: 'Yu' });
  const logout = () => setUser(null);

  return (
    <NavbarContext.Provider value={{ user, logout }}>
      <div className="navbar">
        <h5>ContextAPI</h5>
        <NavLinks />
      </div>
    </NavbarContext.Provider>
  );
};

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

UserContainer:

import { useAppContext } from './Navbar';

const UserContainer = () => {
  const { user, logout } = useAppContext();

  return (
    <div className="user-container">
      {user?.name ? (
        <>
          <p>{`Hello, ${user.name.toUpperCase()}!`}</p>
          <button className="btn" onClick={logout}>
            Logout
          </button>
        </>
      ) : (
        <p>Please login</p>
      )}
    </div>
  );
};

export default UserContainer;
Enter fullscreen mode Exit fullscreen mode

This approach:

  • Keeps components cleaner
  • Avoids repeating useContext(...)
  • Makes the context easier to maintain and reuse

You can also move the context and the custom hook into a separate file (e.g. NavbarContext.js) for better structure as the app grows.

Collapse
 
edriso profile image
Mohamed Idris

🌍 Global Context API – Clean & Scalable Example

A common and recommended approach is to define your global context in a dedicated folder and expose a custom hook for consuming it. This keeps your components clean and avoids repeating useContext logic everywhere.


📁 Suggested Folder Structure (Convention)

src/
│
├── context/
│   └── GlobalContext.jsx
│
├── components/
│   └── App.jsx
│
├── main.jsx
└── index.css
Enter fullscreen mode Exit fullscreen mode

📌 context/GlobalContext.jsx

import { createContext, useContext } from 'react';

const GlobalContext = createContext();

// Custom hook for consuming the context
export const useGlobalContext = () => useContext(GlobalContext);

const GlobalProvider = ({ children }) => {
  const greeting = 'Hello there';

  return (
    <GlobalContext.Provider value={{ greeting }}>
      {children}
    </GlobalContext.Provider>
  );
};

export default GlobalProvider;
Enter fullscreen mode Exit fullscreen mode

📌 main.jsx

Wrap your application with the global provider so all components can access the shared state.

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './components/App';
import GlobalProvider from './context/GlobalContext';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <GlobalProvider>
      <App />
    </GlobalProvider>
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

📌 components/App.jsx

Any child component can now access the global state using the custom hook.

import { useGlobalContext } from '../context/GlobalContext';

const App = () => {
  const { greeting } = useGlobalContext();

  return <h3>{greeting || 'Hey'}</h3>;
};

export default App;
Enter fullscreen mode Exit fullscreen mode

✅ Why This Pattern Works Well

  • Keeps context logic isolated and reusable
  • Avoids prop drilling
  • Enforces a single, clean API via a custom hook
  • Scales well as your app grows
  • Follows common React folder and component conventions

This is a solid baseline for global state using Context API, and you can easily extend it with more state, reducers, or side effects later.