Practical Use Cases of React Context and Hooks
Recently, our teams have moved to React 16.8 across all our web applications. React Hooks and Context becomes the most talked-about features at the moment. I am one of those people.
The goal of this article is to show a couple of use cases where we are attempting to use hooks and contexts to make the source code a bit cleaner and more efficient. If you are not familiar with Hooks yet, you can take a look at the official introduction from Facebook.
Introduction
In one of the projects I’m currently working on, we need to build an editor component for displaying a list of orders and providing the ability to edit the detail of the order.
When users land on the page, they will see the first view (Overview Mode)as default. After clicking one of the orders from the side panel, they will see the second view (Detail Mode) which will allow them to edit some details. The general structure of components can be represented in the following.
Container & ContextProvider Wrapper
| — — SidePanel
| — — — OrderView
| - - - - - - OrderList
| - - - - - - OrderItem
| — — — OrderDetailView
| — — EditorPanel
| — — — -SomeOtherComponents
View 1: Show list of orders
View 2: Show detail of an order
Case 1: Using Context to Manage Local State
Traditionally, we have been using Redux to handle all the application. Sometimes we simply just fetch and the data which is only being used for a small subset of our application and we will never call any action to update them. With Context API available with React 16.8, I am attempting to use Context to manage the local state instead of Redux for the above reason.
OrderInfoContext.jsx
import React, { useState, useEffect, useContext, createContext } from 'react';
import FetchService from './util/FetchService';
const OrderInfoContext = createContext();
// This is a helper component that generate the Provider wrapper
function OrderInfoProvider(props) {
// We will persist API payload in the state so we can assign it to the Context
const [orders, setOrders] = useState([]);
// We use useEffect to make API calls.
useEffect(() => {
async function fetchData() {
// This is just a helper to fetch data from endpoints. It can be done using axios or similar libraries
const orders = await FetchService
.get('/v1/registry/orders');
setOrders(orders);
}
fetchData();
});
//we create a global object that is avaibvale to every child components
return <OrderInfoContext.Provider value={[orders, setOrders]} {...props} />;
}
// Helper function to get Context
function useOrderInfo() {
const context = useContext(OrderInfoContext);
if (!context) {
throw new Error('useOrderInfo must be used within a OrderInfoProvider');
}
return context;
}
export { OrderInfoProvider, useOrderInfo };
Inspired By Kent C. Dodds’s article about
application state, I created a functional component to help me fetch the order data and set up the OrderInfoContact
.
Because we need to fetch the data, we have to use useEffect
to deal with the async operation here. If you are not
familiar, you can think about this like how we fetch data in componentDidMount
a class component.
Using the Context Provider helper we created, we can wrap every component related to the order context. At this point,
we can get the order information from OrderList
and OrderView
.
Using the Context Provider helper we created, we can wrap every component related to the order context. At this point,
we can get the order information from OrderList
and OrderView
.
Container.jsx
class OrderContainer extends Component {
render() {
return (
<OrderInfoProvider>
{/* This is another context provide information about the mode editor is in at the momenmt */}
<EditorModeProvider>
<div className="row">
<div className="col-md-3">
<SidePanel />
</div>
<div className="col-md-9">
Right Panel
</div>
</div>
</EditorModeProvider>
</OrderInfoProvider>
);
}
}
export default OrderContainer;
Case 2: Use Hooks to Create Cleaner Code
Now we can access the order data in OverView, we would like to split them into multiple lists by their status.
Traditionally, we will pass down the order data component by component (prop-drilling).
We will likely end up writing a class component that use getDerivedStateFromProps
to help users update those lists in
the state. Luckily, we are not able to use hooks to make this code a bit cleaner. First, we define those lists as an
empty array. Once the data comes back and the order in the Context is updated, the effect will be triggered
which will update those lists. It does feel nice when you don’t need to write those life cycle methods 😀
OrderView.jsx
import _includes from 'lodash/includes';
import { useOrderInfo } from '../../OrderInfoContext';
import OrderList from './OrderList/OrderList';
const OrderView = () => {
// Get order data from context provider
const [orders] = useOrderInfo();
// Splite orders by their status and store them in the compoent state.
const [remainingOrder, setRemainingOrder] = useState([]);
const [readyOrder, setReadyOrder] = useState([]);
const [doneOrder, setDoneOrder] = useState([]);
// We need update those lists whenever the order data in context is updated
useEffect(
() => {
setRemainingOrder(orders.filter(({ status }) => status === 'NOT_SENT'));
setReadyOrder(orders.filter(({ status }) => status === 'DRAFTED'));
setDoneOrder(orders.filter(({ status }) => status === 'SENT'));
},
[orders] // run when orders changes
);
return (
<div className="container_order-view">
<div className="container-list-container">
<OrderList orderList={remainingOrder} title="Remaining" />
<OrderList orderList={readyOrder} title="Ready to Order" />
<OrderList orderList={doneOrder} title="Done" />
</div>
</div>
);
};
Summary
I want to keep this article short so I skip the EditorModeConext, which has the state for change editor views but it works similarly. This is my first attempt to use Context and Hooks for my project. It is by no means the best approach. I do not think Context should be used all the time if I intend to reuse certain UI components across multiple applications. By injecting context to a component technically makes a component less reusable. I also don’t think Context can replace Redux as many people claimed if the local state is being manipulated over and over again. I will leverage Redux’s time travel ability and its excellent DevTool.
In terms of hooks, I will start using them wherever I can. They do make the code much cleaner and concise.