Skip to main content

Understanding React Context

What is React Context?

React Context is a built-in feature of React that allows you to share data between components without explicitly passing props through every level of the component tree (known as "prop drilling"). It's especially useful for data that can be considered "global" for a specific part of your application, such as:

  • User authentication status
  • Theme preferences
  • Language settings
  • Form state (which is how TanStack Form uses it)

Think of Context like a broadcasting system that makes specific data available to any component that wants to tune in.

The Problem: Prop Drilling

Without Context, sharing data between components that aren't directly related requires passing props through intermediate components that don't actually use the data.

Example: A Shopping Cart Application

Imagine you're building an e-commerce application with these components:

  1. App - The main application
  2. Navbar - Contains the cart icon and count
  3. ProductList - Shows available products
  4. ProductItem - Individual product with "Add to Cart" button
  5. CartSummary - Shows cart contents
  6. Checkout - Process for completing purchase

Without Context, you'd need to pass cart data through props:

// Without Context - Prop Drilling
function App() {
const [cart, setCart] = useState([]);

const addToCart = (product) => {
setCart([...cart, product]);
};

return (
<div>
<Navbar cartCount={cart.length} />
<ProductList products={products} addToCart={addToCart} />
<CartSummary items={cart} />
<Checkout cart={cart} />
</div>
);
}

function ProductList({ products, addToCart }) {
return (
<div>
{products.map((product) => (
<ProductItem key={product.id} product={product} addToCart={addToCart} />
))}
</div>
);
}

function ProductItem({ product, addToCart }) {
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
);
}

function Navbar({ cartCount }) {
return (
<nav>
<div>🛒 {cartCount} items</div>
</nav>
);
}

Notice how we had to pass addToCart from AppProductListProductItem and cartCount from AppNavbar. This is prop drilling.

The Solution: React Context

React Context solves this by creating a way for components to access shared data without prop drilling.

How to Implement Context

1. Create a Context

const CartContext = React.createContext();

2. Create a Provider Component

The Provider wraps your components and makes the context data available to them.

function CartProvider({ children }) {
const [cart, setCart] = useState([]);

const addToCart = (product) => {
setCart([...cart, product]);
};

// The value object contains all data and functions we want to share
const value = {
cart,
cartCount: cart.length,
addToCart,
};

return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}

3. Wrap Your App with the Provider

function App() {
return (
<CartProvider>
<div className="app">
<Navbar />
<ProductList products={products} />
<CartSummary />
<Checkout />
</div>
</CartProvider>
);
}

4. Use the Context in Your Components

Any component inside the Provider can now access the context data directly:

function ProductItem({ product }) {
// Get the addToCart function directly from context
const { addToCart } = React.useContext(CartContext);

return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
);
}

function Navbar() {
// Get the cart count directly from context
const { cartCount } = React.useContext(CartContext);

return (
<nav>
<div>🛒 {cartCount} items</div>
</nav>
);
}

function CartSummary() {
// Get the cart items directly from context
const { cart } = React.useContext(CartContext);

return (
<div>
<h2>Your Cart</h2>
{cart.map((item) => (
<div key={item.id}>
{item.name} - ${item.price}
</div>
))}
<p>Total: ${cart.reduce((sum, item) => sum + item.price, 0)}</p>
</div>
);
}

Context in TanStack Form

TanStack Form uses Context in a similar way to provide form state and functionality to form components.

// 1. Create the contexts
const { fieldContext, formContext } = createFormHookContexts();

// 2. Create a custom hook with components that use these contexts
const { useAppForm } = createFormHook({
fieldComponents: {
TextField,
NumberField,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
});

// 3. Use the hook to create a form instance
function MyForm() {
const form = useAppForm({
defaultValues: {
username: "",
email: "",
},
// ...other options
});

// 4. The form component internally uses the contexts to provide data
return (
<form>
{/* Each field gets access to its own field state */}
<form.AppField
name="username"
children={(field) => <field.TextField label="Username" />}
/>

{/* Form-level components access the entire form state */}
<form.AppForm>
<form.SubmitButton />
</form.AppForm>
</form>
);
}

Benefits of Using Context

  1. Eliminates Prop Drilling: No need to pass props through intermediate components that don't use them.

  2. Cleaner Component API: Components only receive props they actually use.

  3. Centralized State Management: State is managed in one place, making it easier to update and maintain.

  4. Component Reusability: Components become more reusable because they're not tightly coupled to specific prop structures.

  5. Performance Optimization: With careful implementation, you can prevent unnecessary re-renders.

When to Use Context

Context is ideal for:

  • Theme data: Light/dark mode and color schemes
  • User authentication: User info, permissions, and login state
  • Localization/i18n: Language settings and translations
  • Form state: As seen in TanStack Form
  • App configuration: Settings that affect multiple components

It's best to use Context for data that is truly "global" to a component tree and changes infrequently. For more complex state management, you might consider combining Context with other state management solutions like Redux or Zustand.

Context vs. Props

PropsContext
Explicit data flowImplicit data flow
Better for component-specific dataBetter for "global" data
More reusable componentsLess prop drilling
Clear dependenciesHidden dependencies
Best for direct parent-childBest for distant components

Conclusion

React Context is a powerful tool for sharing data between components without prop drilling. It simplifies your component tree and makes your code more maintainable. In libraries like TanStack Form, Context is a key mechanism that enables the creation of elegant, type-safe forms with minimal boilerplate.

By understanding and properly utilizing Context, you can create more maintainable and efficient React applications, especially when dealing with data that needs to be accessed by many components across your application.