Home > front end >  Stack toast notification from bottom up - css / reactjs
Stack toast notification from bottom up - css / reactjs

Time:04-23

I'm making a toast notification and trying to implement position options.

My problem is that when I position the toast at the bottom, then add another, the new one appears at the original position, replacing the first one, and the first one is moved down, eventually out of screen.

What I'm trying to achieve is to have the second toast replace the first one but for the first one to then display ABOVE, so they stack upward.

I've made a minimal example on code sandbox

Here is the reactjs component code, where Toasts is broadly a holder for individual Toast components:

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [toastState, setToastState] = useState([]);
  const handleClick = (pos) => {
    const ts = [...toastState];
    ts.push({ id: ts.length, pos: pos });
    setToastState(ts);
  };

  const Toast = ({ id, pos }) => {
    return <div className={`toast ${pos}`}>{id}</div>;
  };
  const Toasts = () => {
    const toasts = [...toastState];
    const toastComps = toasts.reverse().map((t, i) => {
      return <Toast key={i} id={t.id} pos={t.pos} />;
    });
    return <div>{toastComps}</div>;
  };

  return (
    <div className="App">
      <button
        onClick={() => {
          handleClick("bottom");
        }}
      >
        Bottom
      </button>
      <Toasts className="toasts" />
    </div>
  );
}

And here is the css:

.toasts {
  min-width: 100vw;
  z-index: 9999;
  position: fixed;
  top: 0px;
  left: 0px;
  margin: 0px;
  padding: 0px;
  pointer-events: none;
}

.toast {
  position: relative;
  min-height: 50px;
  min-width: 20rem;
  margin: 5px;
  padding: 5px;
  border: 1px solid black;
  border-radius: 5px;
}

.bottom {
  bottom: -80vh;
}

Any suggestions on how to achieve these toast components stacking from bottom up?

I tried out using flexbox, like adding the following properties to the toasts container class:

display: flex;
flex-direction: column-reverse;
flex-wrap: wrap-reverse;

But to no effect.

I've also tried a js solution, calculating a value for bottom, based on the toast's index:

const [bot, setBot] = useState(0);

useEffect(() => {
    setBot(index ? 80 - (15*index) : 0);
}, [])

//then as an attribute to the `toast`div, after setting className for the position...
style={position.indexOf('bottom') > -1 && bot
? {bottom: `-${bot}vh`} : {}}

This gets closer to what I'm after but when the toasts timeout, or get dismissed, the remaining ones remain high on the screen - so really the whole list is getting pulled up the screen, not stacking upward as intended.

CodePudding user response:

Here is a simplified solution.

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [toasts, setToasts] = useState([]);

  const btnClicked = () => {
    console.log("btnClicked");
    setToasts([...toasts, { id: toasts.length }]);
  };

  return (
    <div className="App">
      <button onClick={btnClicked}>Bottom</button>
      <div className="toasts">
        {toasts
          .map((toast) => <div className="toast">{toast.id}</div>)
          .reverse()}
      </div>
    </div>
  );
}

and the CSS :

.App {
  font-family: sans-serif;
  text-align: center;
}

.toasts {
  position: fixed;
  min-width: 100vw;
  z-index: 9999;
  position: fixed;
  bottom: 0px;
  left: 0px;
  margin: 0px;
  padding: 0px;
  pointer-events: none;
  overflow: hidden;
}

.toast {
  position: relative;
  min-height: 50px;
  min-width: 10rem;
  max-width: 10rem;
  margin: 5px;
  padding: 5px;
  border: 1px solid black;
  border-radius: 5px;
}

.bottom {
  bottom: -80vh;
}

You can see it in action here : CodeSandbox

  • Related