React: MUI toast context

This introduce you to a toast api with Material UI, or MUI.

If you want to use a toast with MUI, <Snackbar /> component is the option.
https://mui.com/material-ui/react-snackbar/#main-content

However, it's annoying to apply it for every single case where a local state, like showToast: boolean, is required to toggle the component.
In that case, you want a toast api, right?

In order for realizing it, a toast context is an option.

Create ToastProvider

First, let's create a context provider which wraps the <Snackbar />.
The point is to create useToast() as a hook. It enables us to call it from any components.

import { Alert, AlertColor, Snackbar } from '@mui/material';
import React, { createContext, ReactNode, useContext, useState } from 'react';

type ToastProp = {
  message: string;
  type?: AlertColor;
  duration?: number;
};

type ToastContextType = {
  toast: (props: ToastProp) => void;
};

const ToastContext = createContext<ToastContextType>({
  toast: () => undefined,
});

export const useToast = () => {
  return useContext(ToastContext);
};

export const ToastContextProvider = ({ children }: { children: ReactNode }) => {
  const [open, setOpen] = useState(false);
  const [message, setMessage] = useState('');
  const [type, setType] = useState<AlertColor>('success');
  const [duration, setDuration] = useState<number>(0);

  const handleClose = () => {
    setOpen(false);
  };

  const toast = ({ message, type = 'success', duration = 5000 }: ToastProp) => {
    setMessage(message);
    setType(type);
    setDuration(duration);
    setOpen(true);
  };

  const contextValue = { toast };

  return (
    <ToastContext.Provider value={contextValue}>
      <>
        {children}
        <Snackbar
          anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
          open={open}
          autoHideDuration={duration}
          onClose={handleClose}
        >
          <Alert severity={type} sx={{ width: '100%' }}>
            {message}
          </Alert>
        </Snackbar>
      </>
    </ToastContext.Provider>
  );
};

export default ToastContext;

Set ContextProvider to App.tsx

Then, set it up in the root component like App.tsx in React.

import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';

import { ToastContextProvider } from './components/ToastProvider';
import Index from '@/pages/Index';
import NotFound from '@/pages/NotFound';

function App() {
  return (
    <ToastContextProvider>
      <Routes>
        <Route path="/" element={<Index />}>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </ToastContextProvider>
  );
}

export default App;

Call it in components

Now, you can call the hook from any components.

import React, { useEffect } from 'react';

import { useToast } from '@/components/ToastProvider';

export default function HandbookRegistration() {
  const { toast } = useToast();

  useEffect(() => {
    if (status.isSuccess) { // Please use your own toggle status.
      toast({ message: 'Success' });
    }
    if (status.isError) {
      toast({ message: 'Error' });
    }
  }, [status]);

  return <div>sample</div>;
}

That's it.