How to use context in a React application
What is context
in React?
As React developers, we are all familiar with passing props through components. Basically, this is used when you want the child component to get some information from the parent. Like, if you have a component which needs a label for example. Then the props passing would look something like this:
const ParentComponent = () => <ChildComponent label="Label from the parent" />; const ChildComponent = ({ label }) => <div>{label}</div>;
So this is how we normally use props in React. However, if you have a large tree of components, passing props can be very cumbersome and messy in the long run.
If you imagine you have an application, which are using a global state, like night/light mode with a toggle button - imagine the hassle with passing around the value to all UI components for them to display the correct UI.
Creating a context
Let's start with create our ThemeContext.jsx
file and create the context.
import { createContext } from "react"; export const ThemeContext = createContext(null);
Now we have created our context, and given it null as default value.
Creating context provider
Next up, we need to extend our ThemeContext.jsx
with a provider.
import { createContext, useReducer } from "react"; export const ThemeContext = createContext(null); export const ThemeContextProvider = ({ children }) => { const [isNightMode, setNightMode] = useReducer( (prevState, isNightMode) => isNightMode, false ); const dispatch = (isNightMode) => setNightMode(isNightMode); return ( <ThemeContext.Provider value={{ isNightMode, dispatch }}> {children} </ThemeContext.Provider> ); };
So, now we have extended the context with a provider component that will wrap the part of the application that should make use of the state in the context. We first create the reducer and give it its default value, in this example we say that night mode should be false. We also declare a dispatch function, which will handle all the updates from the consumers.
Wrapping the application
In order for our components to listen
to the state changes of the context, we need to wrap it with the context. In the root of the application, wrap the application like this:
root.render( <React.StrictMode> <ThemeContextProvider> <App /> </ThemeContextProvider> </React.StrictMode> );
All right. Now we are set and ready to use the context.
Use the context in a component
In all components that are wrapped under the <ThemeContextProvider> will gain access to the isNightMode
value, and we access it like this:
import { ThemeContext } from "contexts/ThemContext"; const { isNightMode } = useContext(ThemeContext);
And if we want to dispatch a new value, we can gain access to the dispatch function and update the theme like this:
const { isNightMode, dispatch } = useContext(ThemeContext); dispatch(true);
And that is how we change the value of the isNightMode
value.
How to use context with API calls?
To extend the functionality in our context, we will now implement an API call and add a loading state and error handling.
Changing the provider
We will first change our reducer to be able to handle an object with data instead of just a boolean value. We will have three values, loading
for the loading state, data
for the actual data being returned from the API and error
if any error occurs.
const reducer = (prevState, updatedProperty) => ({ ...prevState, ...updatedProperty, }); const [employeeState, setEmployeeState] = useReducer(reducer, { loading: null, error: null, data: null, });
Change the dispatch
In order to make the call to the API, we need to update our dispatch. We will use a online dummy API: https://dummy.restapiexample.com
in this demonstration.
// We are using axios for fetching data import axios from "axios"; const dispatch = async (employeeId) => { // On dispatch, we will set loading to true and reset any errors setEmployeeState({ loading: true, error: null, }); try { // Make the actual call const response = await axios.get(`https://dummy.restapiexample.com/api/v1/employee/${employeeId}`); // Setting loading to false, and setting the data object to the API response setEmployeeState({ loading: false, error: null, data: response.data?.data, }); } catch (err) { // If any errors, we are setting the error object here setEmployeeState({ loading: false, error: err, }); } }; return ( <ThemeContext.Provider value={{ employeeState, dispatch }}> {children} </ThemeContext.Provider> );
Accessing data and dispatching
const { employeeState, dispatch } = useContext(ThemeContext); // Dispatching on component mount useEffect(() => { dispatch("1"); }, []); // Example of displaying data const { data, loading, error } = employeeState; // Returning loading state if (employeeState.loading) { return <div>Loading...</div>; } // Returning any errors if (employeeState.error) { return <div>{error}</div>; } // Returning employee data from API return <div>{data.employee_name}</div>;
That's it. Now we have a context that are fetching data from an API, and handles loading state and errors as well.
To read more about context in React, they have an excellent guide on their dev page. Read more here.