Understanding React useContext

by Bob MacNeal, 29 August 2019

In React’s brief history, one challenge has been how to share application state ― the ever-changing application data ― as needed within the component hierarchy of a typical React application. Sharing state falls under the auspices of state management.

State Management

State management is a challenge for single page application libraries like React (see Brief History of React State Management).

React v16.8, released in February 2019, simplifies state management with the introduction of hooks. The useState hook, for component-level state management, and the useContext hook, for cross-component sharing and feature-level or application-level state management, are valuable additions to the React toolbox.

“Hooks let you use state and other React features without writing a class.”

React v16.8: The One With Hooks

Hooks have caused the imminent obsolescence of class components in favor of plain JavaScript functions. Using hooks in functional components, we no longer rely on hacks like lifecycle methods to manage state.

react-wizard Demo

A step-by-step input wizard, implemented in the open source react-wizard application is used to demonstrate how and why to take advantage of the useContext hook.

A wizard workflow is applied in react-wizard to a hypothetical task definition & scheduling feature (Figure 1).

Figure 1 – Step #3 of the wizard workflow

An actual task definition and scheduling feature would require many more configuration steps and additional input types, but the pattern devised in react-wizard is conceived to be extensible.

Walk-through

All code in the walk-through can be viewed in detail in the react-wizard repository. Readers may also clone the repository, then follow the setup steps in the README.md to run the application locally.

The wizard feature is contained in Wizard.js. Wizard.js has a top level WizardProvider component.

WizardProvider represents the context wherein application state, named WizardContext, is stored. WizardProvider has three child components from top to bottom: StepIndicator, StepForm, and StepNavigator (Figure 2)

Figure 2Wizard.js

At the top of the view, the StepIndicator component is a visual indicator that communicates the current step to the user (Figure 3).

Figure 3StepIndicator.js

Below the StepIndicator is the StepForm component. The StepForm component has all of the form elements needed for user input (Figure 4).

Figure 4 – StepForm.js

The StepForm component is capable of rendering a dynamic number of form elements based on the content of the steps described by the initial state set in the WizardContext (more on WizardContext later).

Finally, beneath the StepForm component, is the StepNavigator component (Figure 5). The StepNavigator component has two buttons to navigate to the next or previous step.

Figure 5StepNavigator.js Previous button disabled in Step #1

The WizardProvider and its WizardContext enable the sharing of a state object within the StepIndicator, StepForm, and StepNavigator components.

When a user clicks the Next button on Step #1, a click handler in StepNavigator changes the active step in the state object. The StepIndicator component then responds to the active step state change by rendering itself as Step #2 (Figure 6).

Figure 6 – Re-renderer as Step #2

State Updates via useContext

At each workflow step, StepForm.js (Figure 7) handles storing the user’s input via the handleChange function (line 42). The handleChange function accesses the state object via the useContext (WizardContext) hook (line 20).

Figure 7 – Handling changes in form input

WizardContext

WizardProvider, and its application state representation WizardContext (line 3, Figure 8), provide a state object for subsequent state updates as the user progresses through the steps. The state object is given an initial state with input values set to empty string. The initial state provides the name/value armature for each step.

The initial state includes definitions for each step represented by a 4-step steps array (line 8). This array is extensible to N-steps following a similar pattern.

Each element of the steps array indicates which form elements are needed at each step so that when the activeStep (line 7) changes, each step knows what input elements to render (text input, a checkbox, or a select) in StepForm.

Note that the first step in the steps array below calls for an element of type text (line 17).

Figure 8 – WizardContext representation of application state

Save Form Input

When the user progresses to the last step, the Next button is disabled and a Save button appears (Figure 9).


Figure 9 – Saving input in the last step

Clicking Save outputs a JSON object to the console of the form shown in Figure 10.

Figure 10 – Payload to POST to a data store

It’s left to readers to add form validation, wire up a POST to a backend data store, and to extend the steps array to N-steps with all required task configuration input.

Remarks

Prior to React hooks, state was shared either by prop drilling or by wiring up a state container like react-redux. Both approaches have one or more of the drawbacks of tediousness, complexity, or excessive boilerplate.

The useState hook (for component-level state management) and the useContext hook (for feature/application state management) have significantly simplified state management in the typical React application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s