Playing with the Context API in React 16.3
With React 16.3 recently released I thought it was time to take a look at the new context API. The new context API does what it says on the tin - it allows components to get data from a context rather than from props. It prevents having to pass props down through child component through child component … i.e. “prop drilling”.
So, let’s give this try for allowing a couple of components to consume data about a tenant in a multi-tenant app. The first child component will show the name of the tenant. The second child component will show a form capturing a name and age. Whether the age is captured depends on a promptForAge
flag in the tenant.
Container component
The shell of our container component is below. It gets the tenant data from a web service and puts it in its state. We are referencing child components Title
and ContactForm
but we aren’t using the new context API yet.
class App extends Component {
state = {
tenant: null
}
componentDidMount() {
fetch("http://localhost:21246/api/tenant")
.then((response) => {
return response.json();
})
.then(data => {
this.setState({ tenant: data });
}).catch(error => {
console.log(error);
});
}
render() {
const {tenant} = this.state;
return (
{tenant &&
<div className="container">
<Title />
<ContactForm />
</div>
}
);
}
}
Let’s create our context. We’ll call it TenantContext
.
const TenantContext = React.createContext();
Now let’s specify the context provider around the child components in our container component passing in the tenant as the value:
render() {
const {tenant} = this.state;
return (
<TenantContext.Provider value={tenant}> {tenant &&
<div className="container">
<Title />
<ContactForm />
</div>
}
</TenantContext.Provider> );
}
Title component
The context is now setup nicely in our container component. This is our Title
component without consuming TenantContext
:
function Title() {
return <h1>{/* TODO: inject the tenants name */}</h1>;
}
So, let’s consume TenantContext
using the render props pattern:
function Title() {
return (
<TenantContext.Consumer> {tenant => <h1>{tenant.name}</h1>} </TenantContext.Consumer> );}
ContactForm component
Moving on to our ContactForm
component, we again consume TenantContext
using the render props pattern.
We use tenant.promptForAge
to conditionally render the age input.
In our simple example, we only submit the details to the console.
class ContactForm extends React.Component {
state = {
name: "",
age: 0
};
handleSubmit(e) {
console.log(this.state);
e.preventDefault();
}
render() {
const { name, age } = this.state;
return (
<TenantContext.Consumer>
{tenant => (
<form onSubmit={e => this.handleSubmit(e)}>
<p>Enter your details ...</p>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="text"
className="form-control"
id="name"
value={name}
onChange={e => this.setState({ name: e.target.value })}
/>
</div>
{tenant.promptForAge && (
<div className="form-group">
<label htmlFor="age">Age</label>
<input
type="number"
className="form-control"
id="age"
value={age}
onChange={e => this.setState({ age: e.target.value })}
/>
</div>
)}
<button type="submit" className="btn btn-primary">
Save
</button>
</form>
)}
</TenantContext.Consumer>
);
}
}
Wrap up
The new react Context API is super simple to use and is a great alternative to “prop drilling” for sharing state across multiple components.
If you to learn about using TypeScript with React, you may find my course useful: