Using React’s Context API to Simply Components

Muggle-born
4 min readJan 31, 2022

--

Featuring: Radio Buttons and a Radio Group

In this article I’ll demonstrate rendering a list of items (<Radio />) and how we can useContext() to simplify the props we are required to pass to a <Radio /> component.

What is React’s Context API?

React’s Context API offers a different way to pass props to child components. Context provides a way to pass data through the component tree without having to pass props down manually at every level.

In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.

If you’d like to learn more about React’s Context API, I recommend visiting the React Docs.

Simplifying the Component

We must first define a component and see how it’s used before we can identify any existing problems and how useContext could solve them. For this article we’re going to define a very basic <Radio /> component which renders a single input[type="radio"] element.

const Radio = ({ value, checked, name, onChange }) => (
<input
type="radio"
checked={checked}
onChange={onChange}
name={name}
value={value}
/>
);

Now let’s take a look at how we could use the <Radio /> component to build a simple page with three <Radio /> buttons: small, medium, and large.

Note: we’re going to improve and rewrite <ExampleComponent />at the end of this article.

const ExampleComponent = () => {
const [value, setValue] = useState('small');
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log(event.target.size.value);
};
return (
<div>
<h1>Example Component</h1>
<form onSubmit={handleSubmit}>
<label>
<Radio
checked={value === 'small'}
onChange={handleChange}
name="size"
value="small"
/>
<span>Small</span>
</label>
<label>
<Radio
checked={value === 'medium'}
onChange={handleChange}
name="size"
value="medium"
/>
<span>Medium</span>
</label>
<label>
<Radio
checked={value === 'large'}
onChange={handleChange}
name="size"
value="large"
/>
<span>Large</span>
</label>
<button type="submit">Submit</button>
</form>
</div>
);
};

As you look at the code above, notice the props being passed to each of the <Radio /> components. Specifically, the checked, onChange, and name props. Those three props are almost all the same for each of the three components:

  • name="size"
  • onChange={onChange}
  • checked={value === T} where T is different for each <Radio />

Wouldn’t it be nice if we didn’t have to pass all these props directly to the <Radio /> component? … Yes, it would be and that’s exactly what we’re going to do in the sections below.

Step 1: Creating a New Component

Let’s add a new component to the library and call it <RadioGroup />. The Radio Group will make rendering a list of <Radio /> components easier. The Radio Group will take three additional props: onChange, name, and value. In the sections below we will expose these three props to the <Radio /> component using the Context API.

const RadioGroup = ({ children, onChange, name, value }) => {
return (
<div>{children}</div>
);
}

Step 2: Adding `Context` to the RadioGroup

The parent component <RadioGroup /> which renders the children can use React’s Context API to store the onChange, name, and value prop. Then all children beneath the <RadioGroup /> component will have access to these three props. Therefore, each <Radio /> component will no longer need an explicit checked, name, nor value prop passed to them.

const RadioGroupContext = createContext();const RadioGroup = ({ children, onChange, name, value }) => {
return (
<RadioGroupContext.Provider value={{ onChange, name, value }}>
<div>{children}</div>
</RadioGroupContext.Provider>
);
};

Step 3: Updating the Radio Component with `useContext`

Anytime we define a Context.Provider like RadioGroupContext.Provider we want to also define a Context.Consumer which subscribes to context changes and has access to the values set by the Provider. Since we’re using React Hooks we’ll leverage the useContext() hook instead of using Context.Consumer.

function useRadioGroup() {
return useContext(RadioGroupContext);
}
const Radio = ({
value,
checked: checkedProp,
name: nameProp,
onChange: onChangeProp,
}) => {
// Subscribe to the context provider
const radioGroup = useRadioGroup();
const onChange = (event) => {
if (onChangeProp) {
onChangeProp(event);
}
if (radioGroup && radioGroup.onChange) {
radioGroup.onChange(event);
}
};
let name = nameProp;
let checked = checkedProp;
if (radioGroup) {
if (typeof checked === 'undefined') {
checked = radioGroup.value === value;
}
if (typeof name === 'undefined') {
name = radioGroup.name;
}
}
return (
<input
type="radio"
checked={checked}
onChange={onChange}
name={name}
value={value}
/>
);
}

You’ll notice the <Radio /> component has quite a bit more code than the initial implementation had, but that’s a tradeoff we’ve decided to make: more code in the implementation of <Radio /> for a more simplified external component usage.

Step 4: Simplifying the Example Component

Now let’s rewrite the <ExampleComponent /> (which we initially defined at the top of this article) and this time we’ll use the new <RadioGroup /> component.

const ExampleComponent = () => {
const [value, setValue] = useState('small');
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log(event.target.size.value);
};
return (
<div>
<h1>Example Component</h1>
<form onSubmit={handleSubmit}>
<RadioGroup
onChange={handleChange}
name="size"
value={value}
>

<label>
<Radio value="small" />
<span>Small</span>
</label>
<label>
<Radio value="medium" />
<span>Medium</span>
</label>
<label>
<Radio value="large" />
<span>Large</span>
</label>
</RadioGroup>
<button type="submit">Submit</button>
</form>
</div>
);
};

Conclusion: What’s the Difference between ‘No RadioGroup’ and ‘RadioGroup’

If we only examine the code required to use the <Radio /> component, then we can see quite clearly that using the <Radio /> requires much less code when using the <RadioGroup /> component.

Before: the code required to render a Radio without using the RadioGroup component

<label>
<Radio
checked={value === 'medium'}
onChange={handleChange}
name="size"
value="medium"
/>
<span>Medium</span>
</label>

After: the code required to render a Radio when using the RadioGroup component

<label>
<Radio value="medium" />
<span>Medium</span>
</label>

Additional Resources

--

--

Muggle-born

Hi, my name is Jeremiah. I build things on the web for fun. I enjoy figuring how and why things work the way they do, and I try teaching others along the way.