Improve Data Flow through React Context

In order to manage data between parent and child components, React offers us the possibility to share data as props. Props can only flow in one direction, from parent to children. When a state change occurs in the parent element, all components depending on the changed status will be re-rendered.  


However, when we need to share data in a huge tree of components at different nesting levels or we need to share “global” data like localization or authentication, we need to go for a more consistent solution, like React Context. Context is a powerful tool that makes it possible to share data for all child components without explicitly passing props through children.


How do we declare a context?


Let’s imagine we want to declare a context for managing a logged in user, ignoring the authentication process for simplicity.


import { createContext, useContext, useState } from "react";
const UserContext = createContext(defaultValue);

export const UserContextProvider = ({ children }) => {
    const [user, setUser] = useState<User | undefined>(undefined);
    const login = (userData) => setUser(userData); // Add user information
    const logout = () => setUser(undefined); // Remove user information
    return (
      <UserContext.Provider value={{ user, login, logout }}>

export const useUserContext = () => {
  const context = useContext(UserContext);
  if (context === null) {
    throw new Error("useUserContext must be used within UserContextProvider");
  return context;


The useUserContext hook highlights the main limitation of context: it is accessible and it has value only for components which are descendants of the Provider. Rendering the UserContextProvider in the app.tsx will make our context globally available.



const App = (props: AppProps) => {
  const { Component, pageProps } = props;

  return (
      <Component {...pageProps} />



const MyComponent = () => {
  const { login, logout, user } = useUserContext();
  return (
        ? `Welcome ${user.name} ${user.surname}!`
        : "Please login!"}
        onClick={() => login({ name: "Super", surname: "Admin" })}>
      <Button onClick={() => logout()}>Logout</Button>


It’s now very simple to provide data to child components, no matter how deep they are in the components tree. The solution is just creating, providing and consuming a context!