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='p-1 rounded-full 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