| |

Passing a value to a nested component using the React Context API

When I started learning React in 2018, hooks were not yet a thing. This was very taxing since I was struggling to get a component to render and having to pass props from parent to child added another level of complexity.

Fast-forward to 2022 when I start playing in React again and I learn about the React Context API during an interview.

“Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.”

React Docs
chris pratt extending his hands from his temples outward with yellow line animation symbolizing his mind being blown

They make it sound so simple, but it took me a while to wrap my mind around it and use it in a meaningful way.

Background

My company has a library of reusable React UI components, such as Text and Button that make it easy to test those components in isolation and later plug them into a product UI.

A simplified and generic version of what I'm making. There is a blue, rectangular box. On the left there is a white circle with "1". Underneath is "out of n", with n representing the total steps. In the center of the card there is a heading "Here is an overview of this card." Under the heading is a link "Learn more about it here."
A simplified version of what I’m making

I am tasked with helping create a new password-less login flow using webauthn. The design has multiple cards, each with a header detailing a user’s progress, a heading, and a link. Since this is a common design model it was decided to create a reusable component in our UI library so that it could be adapted for reuse in other projects.

The challenge

Since there is a component library, I will combine and modify existing components to create this new component.

The file hierarchy of this project. It has multiple folders, one for each component. In each folder there is an index.tsx file and a component.stories.tsx. Some of the components also include a theme and component.spec.tsx file.
The file hierarchy for this project

I will use the existing Surface (background color) and Text components. I want to pair complementary text colors based on the selected background color. Since the background color will dictate the text color, I start with Surface and pass the background color to Text and finally into Heading. To do this I use React’s Context API to pass the data through the component tree, rather than having to pass props down manually at each level.

How I did it

First I create a theme for my Surface component. The component library already had an existing theme, so I pull from those colors for this component-specific theme. Here I map through the different background colors and pair them with complementary text colors.

// TypeScript
// Here I bring in the existing theme values to the surface theme
import { TextVariant, SurfaceVariant } from '../../types';

// Since this is TypeScript, I define the shape of my theme
export interface SurfaceTheme{
 variant: { [key in SurfaceVariant]: TextVariant };
}

// This is the theme with the color pairings
export const surfaceDefaultTheme:SurfaceTheme= {
 variant: {
  [SurfaceVariant.PRIMARY]: TextVariant.NORMAL,
  [SurfaceVariant.SECONDARY]: TextVariant.DARK,
  [SurfaceVariant.TERTIARY]: TextVariant.ACCENT,
 },
};

Next, I import createContext, a function of the React Context API from WordPress. Much like above, I create an interface for the shape of the context object and assign a variable to hold the context value.

// TypeScript
import { createContext } from '@wordpress/element';

// The shape of the context object
interfaceSurfaceContext{
 textVariant: TextVariant;
}

// Using createContext, I passed the acceptable value types and gave it a 
// default value of undefined
export const SurfaceContext =createContext<SurfaceContext| undefined>(
 undefined
);

Inside of my Surface function, I pull in the surface theme data (by way of the theme), assign a variable variant to props and give it a default value to use if none exists. Then I assign contextValue to hold the text color, and I pass that as a prop to my context provider. This is the top level of my component tree.

// TypeScript
export function Surface(props:SurfaceProps) {
// Get surface theme
 const {
  components: { surface },
 } =useTheme();

// Get the surface variant | assign to Primary
 const { variant = SurfaceVariant.PRIMARY } = props;

// Create an object with the text color
 const contextValue = { textVariant: surface.variant[variant] };

 return (
// Pass the text color to the React Context Provider
  <SurfaceContext.Provider value={contextValue}>
   <StyledSurface {...props} />
  </SurfaceContext.Provider>
 );
}

Moving into the first child component, Text, I import the useContext React Context function and my SurfaceContext variable from Surface. Inside of my Text function, I assign a variable to hold these values. Then as a prop to the text component, I assign a series of values to the variant parameter.

// TypeScript
import { useContext } from '@wordpress/element';

import { SurfaceContext } from '../surface';

export function Text({
// params
// ...
variant
}: TextProps) {
	const surfaceContext = useContext(SurfaceContext);
...
return (
<Component
       // If the user explicitly passes a text color, 
       // that should be honored.
       // Otherwise use the text color from the surface 
       // theme if defined
       // Or, default to normal text color
	variant= {
	     variant || surfaceContext?.textVariant || TextVariant.NORMAL
} />

Finally, we reach the Header component, the whole reason for this endeavor. Since this component is made up of the Surface and Text components, there is nothing left to do here. All of the background color/text color information is received by its respective components and rendered correctly in this component.

It’s a little anti-climactic, but that’s the magic of the React Context API: it just works.

Here is the finished component in action:

A walk through of the described component, showing different surface variants updating the text color

I learn best by doing, so it took a real project to get me to fully understand the process. I describe it here mostly to solidify the concepts for myself, but maybe it will help you connect some of the pieces that you struggle with while using the Context API.

If you’d like some further reading on the subject, checkout these resources:

This blog originally appeared on dev.to.

Similar Posts

Leave a Reply