Messages toasts

Voir la vidéo
Description Sommaire

Dans cette vidéo nous allons apprendre comment créer un système de messages "Toast" dans React.

00:00 Introduction
01:35 1ère approche, création du contexte
06:30 Inconvénient (rerendu)
06:50 2ème approche, avec une ref
11:40 Ajout de la durée
13:20 Ajout de l'animation
17:57 Optimisation du timer

Objectif

Notre objectif est d'exposer une méthode qui permette d'envoyer facilement un nouveau message Toast depuis n'importe quel composant dans notre application. On commence donc à réfléchir à l'API qui nous semble la plus adapté et on construit à partir de ça.

const {pushToast} = useToasts();

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

La méthode pushToast() prendra en paramètre les propriétés à envoyer à notre composant Toast. On pourra aussi spécifier une durée si on le souhaite.

Organisation

La première approche serait de créer un contexte qui contient les messages à afficher et qui serait mis à jour à chaque fois que l'on envoie un nouveau message. L'inconvénient de cette approche est que le contexte sera re-rendu à chaque nouveau message. Les consommateurs du contexte seront alors eux-aussi re-rendu à cause du changement de valeur. Vu que le système de toast est global une meilleur solution est d'utiliser un système de ref.

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 />
      {children}
    </ToastContext.Provider>
  );
}

Le composant Toasts pourra récupérer le contexte et venir modifier la valeur dans la ref pour ajouter le comportement souhaité. Si vous voulez ajouter une animation d'apparition / disparition il est possible d'utiliser framer-motion mais vous pouvez utiliser des librairies plus légère (comme AutoAnimate) si vous le souhaitez.

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 = Date.now();
    // On sauvegarde le timer pour pouvoir l'annuler si le message est fermé
    const timer = setTimeout(() => {
      setToasts((v) => v.filter((t) => t.id !== id));
    }, (duration ?? 5) * 1000);
    const toast = { ...props, id, timer };
    setToasts((v) => [...v, toast]);
  };

  const onRemove = (toast: ToastItem) => {
    clearTimeout(toast.timer);
    setToasts((v) => v.filter((t) => t !== toast));
  };

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

Il ne nous reste plus qu'à créer le hook qui fournira la méthode pushToast() pour pouvoir envoyer un message depuis n'importe quel composant de notre application.

export function useToasts() {
  const { pushToastRef } = useContext(ToastContext);
  return {
    pushToast: useCallback(
      (toast) => {
        pushToastRef.current(toast);
      },
      [pushToastRef]
    ),
  };
}

On utilisera un useCallback() afin de s'assurer de n'avoir qu'une seule version de cette fonction pour éviter les rendu si cette méthode est passée directement à un composant pure.

Publié
Technologies utilisées
Auteur :
Grafikart
Partager