Kajol Singh

React Context API

By Kajol Singh

Last updated cal_iconDecember 16, 2021

Building a Shared Material UI Snackbar for In-App Notifications

What is Context?

Context provides a way to pass data through a component tree without manually giving props at each level.

Why Context?

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

Preface

Most apps need a way to display notifications to users as they happen unobtrusively. Suppose you’re running a 20% off sale, and you’d like to let your users know as soon as they sign in, or maybe after they submit feedback, you want to display a thank you message. Many apps need to trigger notifications from dozens of different components. The React Context API makes it dead simple to provide all components access to a shared snackbar to start these messages without implementing separate components for each message. Here’s how.

Setup/Dependencies

This post assumes you already have a React app set up with @material-ui/core 1.0.0+ and @material-ui/icons 1.0.0+ installed as dependencies.

Creating the Context

First, we need to create our Context API provider and consumer components. The provider provides the state of our snackbar and some functions for manipulating that state to all consumers. This allows all child components to access and manipulate the provider’s state no matter how deep within the component hierarchy. No prop drilling is required!

import React, { Component } from ‘react’;
const SharedSnackbarContext = React.createContext();
export class SharedSnackbarProvider extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
message: ”,
};
}

openSnackbar = message => {
this.setState({
message,
isOpen: true,
});
};

closeSnackbar = () => {
this.setState({
message: ”,
isOpen: false,
});
};

render() {
const { children } = this.props;

return (
<SharedSnackbarContext.Provider
value={{
openSnackbar: this.openSnackbar,
closeSnackbar: this.closeSnackbar,
snackbarIsOpen: this.state.isOpen,
message: this.state.message,
}}
{children}
</SharedSnackbarContext.Provider>
); } }
export const SharedSnackbarConsumer = SharedSnackbarContext.Consumer;

The first step is creating a context by calling React.createContext(). The object returned contains two properties, Provider and Consumer. We use these to build out the components that will manage and interact with the snackbar’s state.

From here on out, when I use the terms “provider” and “consumer,” I’m referring to the SharedSnackbarProvider and SharedSnackbarConsumer components from this section. 

As you can see, our provider is a pretty standard component. In its render function, we render a SharedSnackbarContext.Provider component with a value prop. The object passed to the value prop is what consumers will be able to access to interact with our snackbar. For lack of a better term, this is the API for our shared snack bar component.

Creating the Shared Snackbar Component

Now we need to build the presentation component responsible for rendering the snackbar UI based on the provider’s state. This component will use the consumer to access the properties for rendering.

import { IconButton, Snackbar } from ‘@material-ui/core’;
import { Close } from ‘@material-ui/icons’;
import React from ‘react’;
import { SharedSnackbarConsumer } from ‘./SharedSnackbar.context’;

const SharedSnackbar = () => (
<SharedSnackbarConsumer>
{({ snackbarIsOpen, message, closeSnackbar }) => (
<Snackbar
anchorOrigin={{
vertical: ‘bottom’,
horizontal: ‘left’,
}}
open={snackbarIsOpen}
autoHideDuration={6000}
onClose={closeSnackbar}
message={message}
action={[
<IconButton key=”close” color=”inherit” onClick={closeSnackbar}>
<Close />
</IconButton>,
]} / )}
</SharedSnackbarConsumer>
);

export default SharedSnackbar;

Here we’ve built a component that renders the snack bar UI in a function within the SharedSnackbarConsumer component. The argument to the function is the value prop object we exposed from our provider. As a result, when the provider’s state is updated, it will trigger the snack bar component to retender. Now we can render this component in our provider’s render function.

<SharedSnackbarContext.Provider
value={{
openSnackbar: this.openSnackbar,
closeSnackbar: this.closeSnackbar,
snackbarIsOpen: this.state.isOpen,
message: this.state.message,
}}
>
<SharedSnackbar /> // This is the line that changed!

{children}
</SharedSnackbarContext.Provider>

Rendering the Provider

At this point, we’re almost finished with the infrastructure. There’s just one last thing to do: render the provider within our app. I will place the provider at the root of the entire app so that all children have access to the snackbar.

import React, { Component } from ‘react’;
import ButtonA from ‘./ButtonA.component’;
import ButtonB from ‘./ButtonB.component’;
import { SharedSnackbarProvider } from ‘./SharedSnackbar.context’;

const styles = {
app: {
display: ‘flex’,
justifyContent: ‘center’,
alignItems: ‘center’,
height: ‘100vh’,
},
};

class App extends Component {
render() {
return (
<div style={styles.app}>
<SharedSnackbarProvider>
<ButtonA />
<ButtonB />
</SharedSnackbarProvider>
</div>
);
} }
export default App;

Opening the Snackbar from Child Components

Now, ButtonA and ButtonB can render their UI and trigger in-app messages without directly receiving props from the root of the app!

import { Button } from ‘@material-ui/core’;
import React from ‘react’;
import { SharedSnackbarConsumer } from ‘./SharedSnackbar.context’;

const styles = {
button: {
margin: 8,
},
};

const ButtonA = () => (
<SharedSnackbarConsumer>
{({ openSnackbar }) => (
<Button
style={styles.button}
variant=”raised”
color=”primary”
onClick={() => openSnackbar(‘You clicked Button A!’)}
>
Button A
</Button>
)}
</SharedSnackbarConsumer>
);
export default ButtonA;
import { Button } from ‘@material-ui/core’;
import React from ‘react’;
import { SharedSnackbarConsumer } from ‘./SharedSnackbar.context’;

const styles = {
button: {
margin: 8,
},
};

const ButtonB = () => (
<SharedSnackbarConsumer>
{({ openSnackbar }) => (
<Button
style={styles.button}
variant=”raised”
color=”secondary”
onClick={() => openSnackbar(‘You clicked Button B!’)}
>
Button B
</Button>
)}
</SharedSnackbarConsumer>
);

export default ButtonB;

Summary

First, we created a context provider component that manages the global state for our snack bar. And, then we created a component that renders the snackbar’s UI based on the provider’s state. This component subscribes to the provider state in its render function via a context consumer.

Finally, we rendered two buttons that update the state of the context provider with the open snack bar function, which passed to them via a context consumer. This results in the changes propagating down to the snack bar component, triggering a re-render. Material UI provides several snackbar features I did not implement with this example, such as action buttons and color customization. For simplicity, I didn’t add the ability to customize those features. Adding that logic yourself would be a significant next step if you’re looking to learn the Context API!

Get In Touch

How Can We Help ?

We make your product happen. Our dynamic, robust and scalable solutions help you drive value at the greatest speed in the market

We specialize in full-stack software & web app development with a key focus on JavaScript, Kubernetes and Microservices
Your path to drive 360° value starts from here
Enhance your market & geographic reach by partnering with NodeXperts