Blog.

Practical Use Cases of React Context and Hooks

Cover Image for Practical Use Cases of React Context and Hooks
Andrew Zheng
Andrew Zheng

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

list-view

View 2: Show detail of an order

detail-view

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.