Toast Messages — React Training

In this video we will learn how to create a “Toast” message system in React.

00:00 Introduction
01:35 1st approach, creation of the context
06:30 Disadvantage (re-rendered)
06:50 2nd approach, with a ref
11:40 Added duration
13:20 Added animation
17:57 Timer optimization


Our goal is to expose a method that makes it easy to send a new Toast message from any component in our application. So we start to think about the API that we think is the most suitable and we build from that.

const {pushToast} = useToasts();

const onSubmit = () => {
    pushToast({title: 'Votre message a bien été sauvegardé', type: 'success', duration: 5})

The method pushToast() will take as parameters the properties to send to our component Toast. You can also specify a duration if you wish.


The first approach would be to create a context which contains the messages to be displayed and which would be updated each time a new message is sent. The disadvantage of this approach is that the context will be re-rendered with each new message. The consumers of the context will then also be re-rendered because of the change in value. Since the toast system is global a better solution is to use a ref system.

const defaultPush = (toast) => {}; // Méthode de base que l'on mettra dans le contexte par défaut

const ToastContext = createContext({
  pushToastRef: { current: defaultPush },

// On entourera notre application de ce provider pour rendre le toasts fonctionnel
export function ToastContextProvider({ children }: PropsWithChildren) {
  const pushToastRef = useRef(defaultPush);
  return (
    <ToastContext.Provider value={{ pushToastRef }}>
      <Toasts />

The component Toasts will be able to retrieve the context and modify the value in the ref to add the desired behavior. If you want to add a appearing/disappearing animation it is possible to use framer-motion but you can use lighter libraries (like AutoAnimate) if you want.

import { AnimatePresence, motion } from "framer-motion";

export function Toasts() {
  const [toasts, setToasts] = useState([]);
  // On modifie la méthode du contexte
  const { pushToastRef } = useContext(ToastContext);
  pushToastRef.current = ({ duration, ...props }) => {
    // On génère un id pour différencier les messages
    const id =;
    // On sauvegarde le timer pour pouvoir l'annuler si le message est fermé
    const timer = setTimeout(() => {
      setToasts((v) => v.filter(
    }, (duration ?? 5) * 1000);
    const toast = { ...props, id, timer };
    setToasts((v) => [...v, toast]);

  const onRemove = (toast: ToastItem) => {
    setToasts((v) => v.filter(

  return (
    <div className="toast-container">
        { => (
            onClick={() => onRemove(toast)}
            initial={{ opacity: 0, x: -30 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: 30 }}
            <Toast {...toast} />

Now all we have to do is create the hook that will provide the method pushToast() to be able to send a message from any component of our application.

export function useToasts() {
  const { pushToastRef } = useContext(ToastContext);
  return {
    pushToast: useCallback(
      (toast) => {

We will use a useCallback() to make sure to have only one version of this function to avoid rendering if this method is passed directly to a pure component.