DEV Community

Cover image for Passing a value to a nested component using the React Context API
Lisa Cee
Lisa Cee

Posted on

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.”

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 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 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.

// 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,
 },
};
Enter fullscreen mode Exit fullscreen mode

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.

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
);
Enter fullscreen mode Exit fullscreen mode

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.

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>
 );
}
Enter fullscreen mode Exit fullscreen mode

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.

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
} />
Enter fullscreen mode Exit fullscreen mode

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:

Oldest comments (0)