What is Context?
React.js Context lets you exchange data throughout your component tree without prop passing. Building reliable applications requires this capacity, particularly when working with data that numerous components from various nesting levels must access.A powerful and flexible tool, context lets you save and access data across an application. It offers built-in facilities for handling distributed data without the need for third-party installation or configuration.
An interface for exchanging data with other components without specifically giving the data as props is called React context. This implies that you can store site-wide data in one location and retrieve it from anywhere in the application, or you can communicate information between a parent component and a deeply nested child component. By including update functions with the data, you can even update data from nested components.

Common components like forms that need to access data across elements or tab views that need a common tab and display context need reusable contexts. Themes, form data, warning messages, and more can be stored in contexts. Context lets you construct components that access data without worrying about passing data through intermediary components or storing data in a centralised store without making it too huge.
The Challenge of Prop Drilling
In React applications before to Context, a prevalent issue called “prop drilling” frequently occurred. Prop drilling is the process of passing data from a parent component to a deeply nested child component while one or more intermediary components in the hierarchy serve only as conduits to transfer the props down the hierarchy, without actually using the data themselves.
This approach may result in several problems with maintainability:
Reduced Readability: Having to follow the props across several components makes it difficult to track where data comes from and where it is ultimately used.
Increased Complexity: It gets more difficult to comprehend the component’s main duties when its interfaces are overflowing with unnecessary props that have no bearing on its logic.
Fragile Refactoring: There is a greater chance of introducing issues when changes to the component hierarchy or data structure necessitate altering numerous files, including ones that don’t directly interact with the data.
A solution offered by React Context is the ability to “skip over two intermediary components without any props,” which strengthens and adapts the codebase as the application develops.
Creating a React Context
Imported straight from the react package, the createContext method is used to create a React.js Context. Upon using createContext(), a context object is returned. This object acts as a connector, expressing the context that other components can either read from or supply, rather than actually holding data.
When creating a context, you can specify a default value to createContext(). A word, number, array, or object can be the default value. When a component tries to consume the context but doesn’t have a Provider (which we’ll discuss next) higher up in the component tree, it uses it. This eliminates the possibility of errors by providing a fallback value.
Two essential attributes are present in the context object that createContext() returns:
SomeContext.Provider: SomeContext.Provider is a specialised React component that gives all of its child components the context value. The real data you want to communicate throughout your application is stored in a single, required prop named value. Children or components nested within this Provider can access this value.
For application-wide data like user information or themes, the Provider should be at the base of the component tree wherever possible. Thus, all program components can access shared data. Place the Provider lower in the component tree for data relevant to that part of your application to encapsulate only the components that need context. This method keeps unused state out of global stores and maintains context focus.
SomeContext.Consumer: The SomeContext.Consumer property provides an alternate way to read the context value. Although it is still feasible, particularly in older class components, it frequently employs the render prop pattern, in which a function is passed to the Consumer component as a child. After receiving the context value as an input, this function renders its content. However, because of its improved readability and conciseness, employing Hooks for context consumption is now typically favoured with the release of React Hooks.
Consuming Context with Hooks ()
To consume context within functional components, the useContext Hook is the most up-to-date and suggested method. UseContext is directly imported from the React package. To utilise it, just call useContext() and supply your context object as an argument (such as UserContext or SaladContext).
For that particular context, the useContext Hook returns the current context value supplied by the nearest Provider component located higher up in the component hierarchy. This allows functional components to directly access shared data without props. UseContext is essential because it immediately re-renders the component consuming the context whenever the context value changes, keeping the data current.
Context can also contain static data and share functions that let nested components update it. A common way to accomplish this dynamic interaction is to combine state management Hooks like useReducer with context. Complex state logic is best managed by the useReducer Hook, especially when state updates need numerous actions or rely on past values.
One typical approach, for instance, is to define a reducer function and initialise state with useReducer in the component acting as the Provider (such as SaladMaker.js). The SaladContext.Provider can then get the value from both the dispatch function (setSalad, for example) and the state (salad items), which are returned by useReducer. All components that are nested within that Provider can now access the data and the tools to update it. In order to change the shared salad state, deeply nested components can utilise useContext to retrieve the setSalad function and call it. This causes re-renders for every component that uses that context.
Common Use Cases and Best Practices
A versatile and effective answer to a range of application requirements is React Context:
Theming: Global themes, such as dark and light modes, can be applied and managed across the program. Context is a strong and adaptable tool that allows you to store and utilise data throughout an application. With built-in tools that don’t require any further third-party installation or configuration, it enables you to manage distributed data.
User Preferences/Authentication: Preserving user profiles, authentication status, or certain user preferences that must be available in a particular application portion or worldwide.
Global Application State: For medium-sized projects or certain features, such form data across elements or alert messages, Context can manage application-level state, while it won’t completely replace Redux in very big apps. It functions as “global variables for a subtree of components” in essence.
However, it’s important to consider when not to use Context:
Simple Prop Passing: Empty prop forwarding may be easier and more understandable than adding context in situations where data just needs to be transmitted down a few levels.
Small Components/Libraries: Standard Hooks (useState, useReducer) or conventional class-based state management may be adequate for isolated components or smaller libraries without the additional abstraction of Context. Prop digging becomes challenging when many components consume data at different nesting depths, as context offers the greatest utility.
Context provides a more lightweight option with fewer boilerplate and configuration for medium-sized projects when compared to libraries like Redux. Redux is frequently chosen for very big applications that require very clear data flow, intricate state transitions, and a lot of middleware since it is a predictable state container. Context, however, does not need extra third-party installations to manage a big volume of dispersed data.
Handling potential memory leaks is crucial when employing functions that entail asynchronous activities and are supplied via Context. If a component unmounts before completing an asynchronous activity like an API call, it may cause warnings or difficulties. By using a boolean flag updated in useEffect’s cleaning function, just updating state if the component is still mounted and checking for it can reduce this.
Context also promotes the modular and reusable component philosophy of React.js. Data logic and user interface components are separated, and shared state is centralised, making components more autonomous, comprehensible, and easily reusable across various application sections.
Code Example:
Using a hypothetical “Salad Builder” application, where user information and chosen ingredients are shared among components, let’s demonstrate how to use React Context.
import React, { createContext, useContext, useReducer } from 'react';
// Contexts
const SaladContext = createContext();
// Reducer
const saladReducer = (state, action) => {
switch (action.type) {
case 'ADD':
return [...state, action.ingredient];
case 'REMOVE':
return state.filter(item => item !== action.ingredient);
case 'CLEAR':
return [];
default:
return state;
}
};
// SaladDisplay Component
function SaladDisplay() {
const { salad, dispatch } = useContext(SaladContext);
return (
<div>
<h3>Salad:</h3>
{salad.length ? (
salad.map((item, i) => (
<div key={i}>
{item}
<button onClick={() => dispatch({ type: 'REMOVE', ingredient: item })}>❌</button>
</div>
))
) : (
<p>No ingredients yet!</p>
)}
</div>
);
}
// SaladBuilder Component
function SaladBuilder() {
const { dispatch } = useContext(SaladContext);
const ingredients = ['Lettuce', 'Tomato', 'Cheese'];
return (
<div>
{ingredients.map(item => (
<button key={item} onClick={() => dispatch({ type: 'ADD', ingredient: item })}>
Add {item}
</button>
))}
<button onClick={() => dispatch({ type: 'CLEAR' })}>Clear</button>
</div>
);
}
// Main App
function App() {
const [salad, dispatch] = useReducer(saladReducer, []);
return (
<SaladContext.Provider value={{ salad, dispatch }}>
<h1>🥗 Simple Salad Builder</h1>
<SaladBuilder />
<SaladDisplay />
</SaladContext.Provider>
);
}
export default App;
Output:
🥗 Simple Salad Builder
Add Lettuce Add Tomato Add Cheese Clear
Salad:
No ingredients yet!
This thorough example demonstrates how React Context can bypass the need for tiresome prop drilling by efficiently sharing both static data (such as user information) and dynamic data along with update functions (such as salad ingredients) across multiple components, including deeply nested ones.
Conclusion
To sum up, React.js Context is a strong feature that makes state sharing between components easier by doing away with prop drilling. Through context providers and consumers, it offers an organised and effective method of managing both static and dynamic data, such as user information or application state, such as chosen salad ingredients. Developers may create scalable, maintainable apps with deeply nested components that can access and change shared data without the hassle of prop chains by integrating Context with hooks like useContext and useReducer.
Because of this, context is particularly useful in medium-sized applications or in particular situations like form management, authentication, and theming. React Context is a lightweight, integrated solution for centralised data flow that improves code readability, reusability, and maintainability across React.js apps, but it doesn’t completely replace more sophisticated state management tools like Redux.