Skip to main content

🌐 Mastering React Context API – A Complete Guide with Examples

When you start building React applications, one of the first challenges you’ll face is managing state across multiple components. Passing props down through multiple levels (known as prop drilling) quickly becomes messy. That’s where the React Context API comes to the rescue.

In this blog, we’ll break down Context API step by step with definitions, explanations, and code examples.

1. React.createContext → Creating Context
Definition:
React.createContext is used to create a Context object that allows data to be shared across the component tree without manually passing props at every level.

Explanation:
When you create a context, React gives you two important things:
Provider → supplies the data
Consumer → retrieves the data

Example:
import React, { createContext } from "react";

// Step 1: Create a Context
const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {(value) => <button>Theme is {value}</button>}
    </ThemeContext.Consumer>
  );
}

Here, ThemeContext allows us to pass the theme ("dark") down to ThemedButton without manually passing it through Toolbar.

2. Provider vs Consumer → Passing and Consuming Context Data
Provider:
Wraps part of the component tree and provides context data.
Takes a value prop which will be available to all child components.
Consumer:
Accesses context data inside the component.

Works with a function that receives the context value.
Example using Provider and Consumer:

const UserContext = createContext();

function App() {
  return (
    <UserContext.Provider value={{ name: "Tushar", role: "Admin" }}>
      <Profile />
    </UserContext.Provider>
  );
}

function Profile() {
  return (
    <UserContext.Consumer>
      {(user) => <h2>{user.name} is {user.role}</h2>}
    </UserContext.Consumer>
  );
}

This avoids passing user as props through multiple components.

👉 In modern React, we often use the useContext hook instead of Consumer.

Example with useContext:

import React, { useContext } from "react";

function Profile() {
  const user = useContext(UserContext);
  return <h2>{user.name} is {user.role}</h2>;
}


3. Avoiding Prop Drilling → Replacing Deep Prop Passing
Prop Drilling Problem:
Without context, you may end up passing props through intermediate components that don’t even need them.

Example of Prop Drilling (❌ Bad Practice):

function App() {
  const theme = "dark";
  return <Toolbar theme={theme} />;
}

function Toolbar({ theme }) {
  return <ThemedButton theme={theme} />;
}

function ThemedButton({ theme }) {
  return <button>Theme is {theme}</button>;
}

Here, theme has to pass through Toolbar, even though Toolbar doesn’t use it.

Using Context (✅ Good Practice):

const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button>Theme is {theme}</button>;
}

Now, no need to pass theme manually at every level! 🎉


4. Context Performance Issues → Unnecessary Re-renders
While Context is powerful, it comes with a performance caveat.
Problem:
Whenever the value of a Provider changes, all child components consuming that context will re-render — even if they don’t use the part of data that changed.
Example:

const UserContext = createContext();

function App() {
  const [user, setUser] = React.useState({ name: "Tushar", role: "Admin" });

  return (
    <UserContext.Provider value={user}>
      <Profile />
      <Dashboard />
    </UserContext.Provider>
  );
}

If user.name changes, both Profile and Dashboard re-render, even if Dashboard only needed role.


✅ Solution: Use Memoization with Context
We can optimize using:
Splitting Contexts → Create separate contexts for different values.
React.memo → Prevents unnecessary re-renders for child components.
useMemo with Provider value → Ensures stable reference.

Optimized Example:

const UserContext = createContext();
function App() {
  const [name, setName] = React.useState("Tushar");
  const [role, setRole] = React.useState("Admin");

  // useMemo to avoid re-creating object on every render
  const userValue = React.useMemo(() => ({ name, role }), [name, role]);

  return (
    <UserContext.Provider value={userValue}>
      <Profile />
      <Dashboard />
    </UserContext.Provider>
  );
}

This way, React won’t trigger unnecessary re-renders unless the context value actually changes.


🎯 Final Thoughts

*createContext gives you a way to share data globally without prop drilling.
*Provider supplies data, Consumer / useContext reads it.
*Avoid Prop Drilling → Context makes state management simpler.
*Performance Tip: Always memoize context values to avoid unnecessary renders.

👉 Context API is great for small to medium apps, but for large-scale state management, consider Redux, Zustand, or Recoil.



Comments

Popular posts from this blog

🌟 Dot net Microservices interview questions

Here is a comprehensive list of 200 .NET microservices coding questions covering all core microservices concepts and cross-cutting concerns relevant for designing, building, deploying, and maintaining .NET-based distributed systems. 🧩 A. Microservices Fundamentals (20) Build a microservice in .NET 8 that exposes a simple CRUD API. Implement communication between two microservices using REST. How would you design microservices for an e-commerce application? Create a microservice that handles user registration and login. How do you isolate domain logic in a microservice? How to apply the "Single Responsibility Principle" in microservices? Design a service registry/discovery mechanism using custom middleware. Implement a service that handles file uploads and metadata separately. Build a stateless microservice and explain its benefits. Implement health check endpoints in .NET 8. Demonstrate versioning in a microservice API. Add Swagger/OpenAPI support to your m...

⚡ Part 1: Introduction to Generics in C#

🌍 Why Do We Need Generics? Imagine you want to create a stack (like a pile of books 📚): You can push items on top You can pop items off the top If we write a stack for integers : public class IntStack { private int[] items = new int[10]; private int index = 0; public void Push(int item) => items[index++] = item; public int Pop() => items[--index]; } 👉 Problem: This only works for int . What if we want a string stack ? Or a Customer stack ? We’d have to duplicate code for every type. 😢 ✅ Solution: Generics Generics let us create type-safe reusable code without duplication. We can say: “I don’t care what type it is yet — I’ll decide later.” 1) Generic Classes Here’s a generic stack : // Generic class "Stack<T>" // The <T> is a placeholder for any type public class Stack<T> { private T[] items = new T[10]; // Array of type T private int index = 0; // Push adds an item of type T public void P...

🚪 Part 9: API Gateway for .NET 8 Microservices (Ocelot & YARP)

Once you have multiple microservices (Products, Orders, Payments…), exposing each one directly to clients gets messy: Different base URLs Duplicated auth logic No unified rate limiting / caching Hard to evolve routes or aggregate data 👉 Enter the API Gateway — your single front door for all microservices. An API Gateway handles: ✅ Routing & path rewriting ✅ Load balancing, retries, circuit breakers ✅ Authentication & Authorization (JWT, OAuth2) ✅ Rate limiting & caching ✅ Aggregation (compose results from multiple services) In this post we’ll implement two strong options: Ocelot → config-driven, mature, DevOps-friendly YARP (Yet Another Reverse Proxy) → Microsoft’s code-first, extensible gateway ⚖️ Ocelot vs YARP — When to Choose Ocelot → JSON config, minimal C#, built-in QoS (rate limit, circuit breaker). Perfect for teams that like DevOps config-as-code. YARP → full C# control, middleware-friendly, can embed into broader apps (e.g. add dashb...