Using React Context Hooks For Data-Heavy Apps

I build a lot of mini data-heavy React apps, so I wanted to share my basic architecture. The apps I work on typically have a small number of different REST API calls usually with several parameters that can be changed to query the data set. This might not work for everyone, and I’m not sure how well this scales to bigger applications, but I like how this works for my needs.

With the addition of hooks, functional components in React became a lot easier to write. This is my preferred method of creating React apps at the moment, because it simplifies the code a lot [particularly with the this.handler = this.handler.bind(this) code mess in the constructor] Coming from React Classes, I had to change my basic architecture a little bit, but I’m happy with what I arrived at.

What I’m going to make here is a quick app that has

  • One data API call
  • One Control component
  • One Presentation component
  • The app will display the current time when a time zone button is pressed.

React Context Hooks Time App


GitHub

Here’s an overview of component tree of this architecture.

  • The App just wraps and organizes the app. There can be more there, but we are keeping it simple here.
  • The Controls component will have two choices for time zone. Clicking a different button will change the parameter and make another REST API call.
  • The Presentation component for me typically contains a data grid or other items which present the return from the API. Here it’s just text with data from our time API.
  • The DataContainer component is where this gets interesting. I’m housing all the API fetching, state and control handlers in this component, so we don’t burden the App component with this.

App.js

const App = () => 
        
        
    

This is straight-forward and clean.

Controls.js

import React, {useState, useContext, useEffect} from 'react';

import {ContextItem} from '@src/context';

const Controls = () => {

    const {appParam, methods} = useContext(ContextItem);

    return 
}

The Controls component has two buttons that fire off a method on a click and use our app’s parameters to provide control feedback. This line: const {appParam, methods} = useContext(ContextItem); is the context hook and it’s the key to making this work. I like that this is almost the same bringing props into your class component.

I’m also sending methods along in the context so that we can manipulate the app’s state when the users press the button.

It should be noted that the import statements are necessary for these types of components particularly when we what to use a context. They are also omitted in subsequent component snippets for brevity.

Presenation.js

const Presentation = () => {
    const {data} = useContext(ContextItem);

    return 
The current time for {data.timezone} is: {data.datetime.slice(0, 16)}
}

Same idea here, as the Controls component. We are using the data from the app’s state which we retrieve through the context API to populate the text with some information.

Data Container

const DataContainer = ({children}) => {

    const [data, setData] = useState({datetime: ''});
    const [appParam, setAppParam] = useState({tz: 'UTC'});

   
    // HANDLES DATA
    const getData = () => {
        fetch(`https://worldtimeapi.org/api/timezone/${appParam.tz}`).then(res => res.json().then(json => setData(json))) 
    }

    useEffect(() => {
        getData(); // This hooks gets the data once after the component mounts
    }, [appParam.tz]) // this will run again when the tz prop changes

    // HANDLES EVENTS / STATE MANIPULATION
    const handleAppParamChange = (obj) => {

        const newAppParam = Object.assign({}, appParam, obj);

        setAppParam(newAppParam);
    }


    return 
        {children}
    
}

Here’s the fun part. I use the Data Container as a wrapper, so that the App component has the component layout of the application in it. This gets useful with larger apps, when you want to see multiple data tables or control components.

This component has four parts:

  • The useState() hooks
  • The REST API data fetching
  • The event handler methods
  • The Provider and render return

The useState() hooks are how you define your state structure. This can get more complex, but the nice things about hooks is that you can have different state variables for each important element. In this I’ve broken it up into two parts: data and parameters.

The REST API data fetching handles the promises and fetch calls along with when to run it. Previously in React classes, you would have used the componetDidMount() life cycle method, but with React Hooks we use the the useEffect() hook. The array used as the second parameter in useEffect() determines when that effect will fire again. I put the time zone parameter in there so that it will fire whenever there’s a new time zone to retrieve a new time. [This is a lot cleaner than before.]

The event handler is pretty straight forward. I take the obj from the controls to update the current appParam state value. Then update that state value. Once that updates the useEffect() sees the value for timezone has changed and it fires again.

The last part of the component is the return part. This creates the Provider, puts the state and methods that manipulate that state into the context. The component is made into a wrapper by include the children props in the middle of the Provider.

Conclusion and Benefits

Now I can’t speak for any performance gains or hinderance, but from a code readability stand point I find this approach:

  • Organizes API calls
  • Allows you make subsequent API calls based on parts of state that changes. Eliminating awkward asynchronous issues involved with updating state and using that state to make an updated API call.
  • Prevents props drilling by allowing you to access any part of your context as easily as you would props.
  • Provides a clean organization of components in your app. The Controls and Presentation component don’t have repetitive and messy props to worry about.