Skip to main content

React Hot Toast

Current favorite lib for toast notifications, with some custom hook and implementation

––– views

React Hot Toast

A lib for toast notifications, my current favorite. Checkout the documentation

Package Dependencies

yarn add react-hot-toast
bash

Set up

Add the div to _app.ts or App.tsx

import { AppProps } from 'next/app'; import '@/styles/globals.css'; import DismissableToast from '@/components/DismissableToast'; function MyApp({ Component, pageProps }: AppProps) { return ( <> <DismissableToast /> <Component {...pageProps} /> </> ); } export default MyApp;
tsx

DismissableToast

React hot toast doesn't support dismiss button by default, so I use a custom component to add it.

import * as React from 'react'; import { toast, ToastBar, Toaster } from 'react-hot-toast'; import { HiX } from 'react-icons/hi'; export default function DismissableToast() { return ( <div> <Toaster reverseOrder={false} position='top-center' toastOptions={{ style: { borderRadius: '8px', background: '#333', color: '#fff', }, }} > {(t) => ( <ToastBar toast={t}> {({ icon, message }) => ( <> {icon} {message} {t.type !== 'loading' && ( <button className='rounded-full p-1 ring-primary-400 transition hover:bg-[#444] focus:outline-none focus-visible:ring' onClick={() => toast.dismiss(t.id)} > <HiX /> </button> )} </> )} </ToastBar> )} </Toaster> </div> ); }
tsx

Usage

import toast from 'react-hot-toast'; function addSomething() { toast.success('message'); toast.error('message'); }
tsx

Toast Promise Example

With promise, we don't need to use toast.success or toast.error, but directly send a promise, and it will be managed.

toast.promise( axios .post('/user/login', data) .then((res) => { const { jwt: token } = res.data.data; tempToken = token; localStorage.setItem('token', token); // chaining axios in 1 promise return axios.get('/user/get-user-info'); }) .then((user) => { const role = user.data.data.user_role; dispatch('LOGIN', { ...user.data.data, token: tempToken }); history.replace('/'); }), { loading: 'Loading...', success: 'Success', error: (err) => err.response.data.msg, } );
tsx

Default Message

You can compose it with default message

export const defaultToastMessage = { loading: 'Loading...', success: 'Data fetched successfully', // eslint-disable-next-line @typescript-eslint/no-explicit-any error: (err: any) => err?.response?.data?.msg ?? 'Something is wrong, please try again', };
tsx

useLoadingToast hook

Using a useLoadingToast hook we can get access if there is any loading toast showing, then use it to disable button.

import { useToasterStore } from 'react-hot-toast'; /** * Hook to get information whether something is loading * @returns true if there is a loading toast * @example const isLoading = useLoadingToast(); */ export default function useLoadingToast(): boolean { const { toasts } = useToasterStore(); const isLoading = toasts.some((toast) => toast.type === 'loading'); return isLoading; }
tsx

useWithToast hook

This hook will handle loading and error state from SWR and show toast on initial fetch. Revalidating will not trigger loading toast.

import * as React from 'react'; import toast from 'react-hot-toast'; import { SWRResponse } from 'swr'; import { defaultToastMessage } from '@/lib/helper'; import useLoadingToast from '@/hooks/useLoadingToast'; type OptionType = { runCondition?: boolean; loading?: string; success?: string; error?: string; }; export default function useWithToast<T, E>( swr: SWRResponse<T, E>, { runCondition = true, ...customMessages }: OptionType = {} ) { const { data, error } = swr; const toastStatus = React.useRef<string>(data ? 'done' : 'idle'); const toastMessage = { ...defaultToastMessage, ...customMessages, }; React.useEffect(() => { if (!runCondition) return; // if toastStatus is done, // then it is not the first render or the data is already cached if (toastStatus.current === 'done') return; if (error) { toast.error(toastMessage.error, { id: toastStatus.current }); toastStatus.current = 'done'; } else if (data) { toast.success(toastMessage.success, { id: toastStatus.current }); toastStatus.current = 'done'; } else { toastStatus.current = toast.loading(toastMessage.loading); } return () => { toast.dismiss(toastStatus.current); }; }, [ data, error, runCondition, toastMessage.error, toastMessage.loading, toastMessage.success, ]); return { ...swr, isLoading: useLoadingToast() }; }
tsx

Usage

You can use it with the useSWR hook

const { data: pokemonData, isLoading } = useWithToast( useSWR<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20'), { loading: 'Override Loading', } );
tsx