Make your own Snackbars using React + Redux and Styled-Components
Tags: react, javascript, tutorial, codenewbie
Published: 2021-02-26 23:27:50

### What is it? 🤔

A Snackbar is a UI component that provides the user visual feedback on an event within the app without interrupting the user experience. This is typically shown as a message box that informs the user of an action being performed or that will be performed by the app.

Snackbars typically behave in the following manner:

- They provide information about an app process via text.
- They will disappear on their own after a certain time.
- They should not interrupt the user from anything else.

That being said, let's get started with a fresh **create-react-app**.

---

### Setting Up The Redux Store 💾

For now, we'll create a simply button in our App component to trigger the Snackbar via `react-redux`. The setup for the redux store is very straightforward, with all the actions and reducers in the same redux folder as follows.

```js
/* ---- redux/actions.js ---- */

export const toggleSnackbarOpen = (message) => ({
  type: "TOGGLE_SNACKBAR_OPEN",
  message,
});

export const toggleSnackbarClose = () => ({
  type: "TOGGLE_SNACKBAR_CLOSE",
});

```

For now, we just want to be able to pass a message in our dispatch to be rendered in the Snackbar, other parameters can also be added such as a timer or even the variation of snackbar being rendered i.e. success, warning, or informative, but for now we'll stick with the basics.

```js
/* ---- redux/reducers.js ---- */

const initialState = {
  toggleSnackbar: false,
  snackbarMessage: null,
};

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case "TOGGLE_SNACKBAR_OPEN": {
      return {
        ...state,
        toggleSnackbar: true,
        snackbarMessage: action.message,
      };
    }

    case "TOGGLE_SNACKBAR_CLOSE": {
      return {
        ...state,
        toggleSnackbar: false,
        snackbarMessage: null,
      };
    }

    default: {
      return state;
    }
  }
}

```

In our reducers' initial state we would need a message and the boolean state of the Snackbar. We can also include different states for different types of messages like `warningMessage` for a `toggleWarningSnackbar` state.

```js
/* ---- redux/store.js ---- */

import { createStore } from "redux";
import reducer from "./reducers";

const config =
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();

export default function configureStore(initialState) {
  const store = createStore(reducer, initialState, config);
  return store;
}

```
And of course, we create the redux store and configure it, and then we connect the store to the App with the Provider as follows:

```js
/* ---- index.js ---- */

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

import { Provider } from "react-redux";
import configureStore from "./redux/store";

const store = configureStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

```

---

## The Snackbar Component 🍫

For this example, we only want our Snackbar to display a message and fade itself away after an event is fired, but it will also allow the user to dismiss the Snackbar altogether. We will also add a timeout variable as a prop to this component to define when the notification will disappear.

```js
import React, { useEffect } from "react";
import styled, { keyframes } from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import { toggleSnackbarClose } from "../redux/actions";
import { FiX } from "react-icons/fi";

const Snackbar = ({ timeout }) => {
  const dispatch = useDispatch();

  // select the UI states from the redux store
  const SHOW = useSelector((state) => state.toggleSnackbar);
  const MESSAGE = useSelector((state) => state.snackbarMessage);

  // convert the timeout prop to pass into the styled component
  let TIME = (timeout - 500) / 1000 + "s";

  let TIMER;
  function handleTimeout() {
    TIMER = setTimeout(() => {
      dispatch(toggleSnackbarClose());
    }, timeout);
  }

  function handleClose() {
    clearTimeout(TIMER);
    dispatch(toggleSnackbarClose());
  }

  useEffect(() => {
    if (SHOW) {
      handleTimeout();
    }
    return () => {
      clearTimeout(TIMER);
    };
  }, [SHOW, TIMER]);

  return (
    SHOW && (
      <Container time={TIME}>
        <p>{MESSAGE}</p>
        <Button onClick={handleClose}>
          <FiX />
        </Button>
      </Container>
    )
  );
};

const fadein = keyframes`
    from {
      bottom: 0;
      opacity: 0;
    }
    to {
      bottom: 1rem;
      opacity: 1;
    }
`;

const fadeout = keyframes`
    from {
      bottom: 1rem;
      opacity: 1;
    }
    to {
      bottom: 0;
      opacity: 0;
    }
`;

const Container = styled.div`
  position: fixed;
  z-index: 1000;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  height: auto;
  padding: 0.625rem 1rem;
  border-radius: 0.75rem;
  border: transparent;
  background-color: hsl(200, 100%, 65%);
  color: white;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);

  display: flex;
  justify-content: center;
  align-items: center;

  animation: ${fadein} 0.5s, ${fadeout} 0.5s ${(props) => props.time};
`;

const Button = styled.button`
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: 0.875rem;
  padding: 0;
  margin-left: 1rem;
  height: 1.75rem;
  width: 1.75rem;
  text-align: center;
  border: none;
  border-radius: 50%;
  background-color: transparent;
  color: white;
  cursor: pointer;

  &:hover {
    background-color: hsl(200, 100%, 60%);
  }
`;

export default Snackbar;

```

To make the Snackbar disappear on its own once toggled we use `setTimeout` to trigger another dispatch to close the Snackbar according to the value of the `timeout` prop. You will notice that 0.5s was shaved off to the `TIME` variable to allow our nice `fadeOut` animation to take place, when it is passed as a prop to our `Container` component. Note that the `keyframes` animations must take precedence before being called into the `animation` CSS property

Additionally, we have another button within the Snackbar that displays alongside the message that can close the Snackbar.

---

### The App 🖥️

With reusability in mind, we want to be able to activate the Snackbar just by simply importing its component and its action dispatcher to any view.

```js
/* ---- App.js ---- */

import React from "react";
import GlobalStyles from "./components/GlobalStyles";
import styled from "styled-components";
import Snackbar from "./components/Snackbar";
import { useDispatch, useSelector } from "react-redux";
import { toggleSnackbarOpen } from "./store/actions";

const App = () => {
  const dispatch = useDispatch();

  return (
    <>
      <GlobalStyles />
      <Wrapper>
        <Button
          onClick={() => {
            dispatch(toggleSnackbarOpen("I'm a Snackbar!"));
          }}
        >
          Click Me!
        </Button>
        <Snackbar timeout={3000} />
      </Wrapper>
    </>
  );
};

const Wrapper = styled.div`
  height: 100vh;
  background: #fffc;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const Button = styled.button`
  padding: 0.5rem 1rem;
  font-size: 1.3rem;
  border-radius: 0.5rem;
  outline: none;
  border: none;
  background: lightblue;
  cursor: pointer;
  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);

  &:hover {
    background: lightgoldenrodyellow;
  }
`;

export default App;

```

And just like that, we have a working (albeit basic) Snackbar that can be re-used in any other component! The code shown so far can also be viewed in this Code Sandbox snippet:

{% codesandbox naughty-star-2ihkc %}

---

### ✨ But Wait, There's More! ✨

There are a lot of cool features to add in a Snackbar, such as its anchoring position or having Snackbar variations, for the full specifications, you should definitely check out Material Design's page [here](https://material.io/components/snackbars).

If you are interested in adding some of those features, please feel free to check out my take on this over here:

{% github g-thinh/simple-snackbar no-readme %}

Happy Coding 😊
---