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.”
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.
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.
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,
},
};
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
);
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>
);
}
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
} />
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:
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:
Top comments (0)