Skip to content

Creating a simple Snackbar with React

This is a tutorial to program your own Snackbar with React. The end result will be like the one that is triggered with the “Show snackbar” button below.

Startup

You can find the source code here. The startup branch contains the initial setup files. You can also get it directly from the command line.

$ git clone -b startup git@github.com:fabrizzio-gz/simple-react-snackbar.git
$ cd simple-react-snackbar
$ npm install

The final folder structure of the project will look as follows:

.
├── public
│   └── index.html
└── src
    ├── App.js
    ├── components
    │   ├── MessageInput
    │   │   ├── MessageInput.css
    │   │   └── MessageInput.jsx
    │   ├── Snackbar
    │   │   ├── Snackbar.css
    │   │   └── Snackbar.jsx
    │   └── store
    │       └── snackbar-context.js
    └── index.js

Initially, our parent App component renders only the MessageInput component. We will use it only to trigger the snackbar with a certain message. The actual Snackbar is independent of this component.

// App.js
import React from "react";

import MessageInput from "./components/MessageInput";

const App = () => <MessageInput />;

export default App;
// MessageInput.jsx
import React from "react";

import "./MessageInput.css";

const MessageInput = () => {
  return (
    <div className="app__container">
      <div className="app__center">
        <label htmlFor="snackbar-msg">Message</label>
        <input id="snackbar-msg" type="text" />
      </div>
      <div className="app__center">
        <button className="app__button">Show snackbar</button>
      </div>
    </div>
  );
};

export default MessageInput;

At this state, our App should render as shown below.

Snackbar markup

Let’s proceed now to create the Snackbar component. At its core, it’s just a div with a label and a button to close it.

// Snackbar.jsx
import React from "react";

import "./Snackbar.css";

const Snackbar = () => {
  return (
    <div className="snackbar__container">
      <div className="snackbar__label">Hello!</div>
      <div className="snackbar__dismiss">&times;</div>
    </div>
  );
};

export default Snackbar;

The corresponding styles are present in the Snackbar.css file. They were copied from the Material Design snackbar demo and are optimized only for large displays. Optimization for mobile devices would require some additional CSS rules that you can add if needed.

The snackbar would be rendered as shown below (although with a smaller width).

Hello!
×

Adding the snackbar context

The state of the snackbar will be managed with the Context API. We do so so that we can trigger the snackbar from anywhere inside the App without having to pass props between components.

We start by creating a new file snackbar-context.js. It contains the context object with the snackbar state and state handlers:

// snackbar-context.js
import React, { useState } from "react";

const SnackbarContext = React.createContext({
  isDisplayed: false,
  displayMsg: (msg) => {},
  onClose: () => {},
});

export default SnackbarContext;

msg will contain the snackbar text to be shown. isDisplayed will control whether the snackbar is displayed or not. displayMsg will trigger the snackbar with a certain message. onClose will be called from the dismiss button of the snackbar to close it. Latere on we will add a timer so that if the snackbar isn’t closed by the user, it will be closed automatically after a certain time.

Additionally, snackbar-context.js contains a custom context provider component that will wrap the whole App insideSnackbarContext.Provider. This wrapper is necessary to be able to consume the context from within child components.

This custom component will also manage the state variables of the snackbar: msg and isDisplayed. The display and close handlers modify those state variables for the snackbar to function as we want.

// snackbar-context.js
export const SnackBarContextProvider = (props) => {
  const [msg, setMsg] = useState("");
  const [isDisplayed, setIsDisplayed] = useState(false);

  const displayHandler = (msg) => {
    setMsg(msg);
    setIsDisplayed(true);
    timer = setTimeout(() => {
      closeHandler();
    }, 3000); // close snackbar after 3 seconds
  };
  const closeHandler = () => {
    clearTimeout(timer);
    setIsDisplayed(false);
  };

  return (
    <SnackbarContext.Provider
      value={{
        msg,
        isDisplayed,
        displayMsg: displayHandler,
        onClose: closeHandler,
      }}
    >
      {props.children}
    </SnackbarContext.Provider>
  );
};

As it was said, the whole App will be wrapped in this custom context provider component. We do so with the following modifications in index.js.

//index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { SnackBarContextProvider } from "./components/store/snackbar-context";

ReactDOM.render(
  <SnackBarContextProvider>
    <App />
  </SnackBarContextProvider>,
  document.getElementById("root")
);

Adding the snackbar to the App

The Snackbar is rendered from App dynamically based on the context isDisplayed state.

//App.js
import React, { useContext } from "react";

import MessageInput from "./components/MessageInput";
import Snackbar from "./components/Snackbar";
import SnackbarContext from "./components/store/snackbar-context";

const App = () => {
  const snackbarCtx = useContext(SnackbarContext);
  return (
    <>
      <MessageInput />
      {snackbarCtx.isDisplayed && <Snackbar />}
    </>
  );
};

export default App;

See that useContext allows us to import the context.

Displaying a dynamic message

So far, our snackbar displays the static text Hello!. Instead, we would like it to show a dynamic text. We can do so by making it display the msg state of SnackbarContext.

// Snackbar.jsx
import React, { useContext } from "react";

import "./Snackbar.css";
import SnackbarContext from "./store/snackbar-context";

const Snackbar = () => {
  const snackbarCtx = useContext(SnackbarContext);

  return (
    <div className="snackbar__container">
      <div className="snackbar__label">{snackbarCtx.msg}</div>
      <div className="snackbar__dismiss" onClick={snackbarCtx.onClose}>
        &times;
      </div>
    </div>
  );
};

export default Snackbar;

Additionally, we have added the onClose handler to the dismiss button of the snackbar. This way the user is able to close the snackbar by pressing on it. See that this handler also comes from the SnackbarContext.

Once again, we consume the context state by calling useContext.

Triggering the snackbar

At this point, our snackbar, although quite basic, is functional. We can trigger it from anywhere inside the app. Let’s see how to do so from the MessageInput component.

// MessageInput.jsx
import React, { useContext, useRef } from "react";

import "./MessageInput.css";
import SnackbarContext from "./store/snackbar-context";

const MessageInput = () => {
  const inputRef = useRef();
  const snackbarCtx = useContext(SnackbarContext);

  const clickHandler = () => {
    const msg = inputRef.current.value;
    snackbarCtx.displayMsg(msg);
  };

  return (
    <div className="app__container">
      <div className="app__center">
        <label htmlFor="snackbar-msg">Message</label>
        <input ref={inputRef} id="snackbar-msg" type="text" />
      </div>
      <div className="app__center">
        <button className="app__button" onClick={clickHandler}>
          Show snackbar
        </button>
      </div>
    </div>
  );
};

export default MessageInput;

The basic process is to call SnackbarContext.displayMsg with the message we would like to display. The useRef hook was used to access the contents of the input element. We call displayMsgwith that value.

A similar method would be used in any other app to trigger the snackbar.

Animating the snackbar

Finally, we’ll add a couple of extra features. First, we’ll animate the snackbar every time it’s displayed.

We can do so by setting the animate CSS property of the snackbar__container class. The animation is defined as follows:

/* Snackbar.css */
@keyframes slide-up {
  from {
    opacity: 0;
    transform: translateY(3rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

The corresponding CSS rule:

/* Snackbar.css */
.snackbar__container {
  ...
  animation: 300ms ease-out forwards slide-up;
}

With these additions, the snackbar will be animated every time it’s displayed.

Rendering the snackbar inside its own container

Our Snackbar component is rendered as a child of the App components inside the root div element. While this is ok for this example, larger projects could need rendering the snackbar inside a different HTML element. The reason could be to improve the semantics of the application or to avoid rendering the snackbar inside a deeply nested component.

To render the snackbar at a different place, without modifying the strructure of our React components, we can use createPortal.

We will start by adding a new div element to index.html.

<!--  index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React Snackbar</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="snackbar__root"></div>
  </body>
</html>

See the id attribute is set to snackbar__root. The name is not important but we need it to identify it.

Next, we’ll modify Snackbar by asking React to render it at a different DOM node. This is done with createPortal, which is imported from react-dom.

// Snackbar.jsx
import React, { useContext } from "react";
import ReactDOM from "react-dom";

import "./Snackbar.css";
import SnackbarContext from "./store/snackbar-context";

const Snackbar = () => {
  const snackbarCtx = useContext(SnackbarContext);

  return ReactDOM.createPortal(
    <div className="snackbar__container">
      <div className="snackbar__label">{snackbarCtx.msg}</div>
      <div className="snackbar__dismiss" onClick={snackbarCtx.onClose}>
        &times;
      </div>
    </div>,
    document.getElementById("snackbar__root")
  );
};

export default Snackbar;

With these changes, the snackbar HTML elements will be rendered inside the snackbar__root div element.


This concludes this tutorial to create a basic Snackbar with React. You can access the complete source code here.

Published inProgramming
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments