DEV Community

Cover image for 【React.js】Making a Simple Toast Message Component with useContext and React Portal -part3-
Kota Ito
Kota Ito

Posted on

【React.js】Making a Simple Toast Message Component with useContext and React Portal -part3-

In part1:
I created a ClientPortal component to render toast messages in a specific DOM element.
✏️https://dev.to/c0xxxtv/reactjs-making-a-simple-toast-message-component-with-usecontext-part1-1l9l

In part2:
Created toast message component to be rendered.
✏️https://dev.to/c0xxxtv/reactjs-making-a-simple-toast-message-component-with-usecontext-and-react-portal-part2-5fmk

In part3, I will define ToastContext using useContext hook.

But Why Using Context??

I have 2 reasons why I decided to use useContext for my toast message feature.

1: Toast message will persist even after page transition
My app has a form that, after a successful submission, navigates the user to a different page. Therefore, the toast message needs to be displayed persistently, even after the page transition.

2: Accessibility Across Components
The function that displays the toast message becomes available in any file that's wrapped by ToastContext.Provider. In many scenarios, my app needs to display the toast message, especially after users perform CRUD operations. They want to see the result of their actions, so I wanted the function that triggers the toast message to be accessible anywhere in my app files."

1: Defining ToastContext


import React, { useState, useContext, useCallback } from 'react';

import { Children } from '@/types/types';

const ToastContext = React.createContext<ToastContextState | undefined>(undefined);

export const ToastProvider: React.FC<Children> = ({ children }) => {
  const [isShow, setIsShow] = useState(false);
  const [text, setText] = useState('');
  const [error, setError] = useState(false);

  const showToastMessage = useCallback((text: string, error: boolean | undefined) => {
    if (error) {
      setError(true);
    }
    setText(text);
  }, []);

  React.useEffect(() => {
    let timer: NodeJS.Timeout;
    if (isShow) {
      timer = setTimeout(() => {
        setIsShow(false);
      }, 5000);
    }
    return () => clearTimeout(timer);
  }, [isShow]);

  return <ToastContext.Provider value={{ showToastMessage, isShow, text, error }}>{children}</ToastContext.Provider>;
};

export const useToast = () => {
  const context = useContext(ToastContext);
  if (context === undefined) {
    throw new Error('useToast must be used within a ToastProvider');
  }
  return context;
};
Enter fullscreen mode Exit fullscreen mode

Create a context in the code below.

const ToastContext = React.createContext<ToastContextState | undefined>(undefined);
Enter fullscreen mode Exit fullscreen mode

Here I make three states.

 export const ToastProvider: React.FC<Children> = ({ children }) => {
  const [isShow, setIsShow] = useState(false);
  const [text, setText] = useState('');
  const [error, setError] = useState(false);
Enter fullscreen mode Exit fullscreen mode

This showToastMessage function below takes two arguments,
one is text, the message text that will be displayed,
and the other is 'error' which is a boolean value that determine the message type. If the error is true, the toast message will contain an error icon; otherwise, it will display a success icon."

const showToastMessage = useCallback((text: string, error: boolean | undefined) => {
    if (error) {
      setError(true);
    }
    setIsShow(true)
    setText(text);
  }, []);
Enter fullscreen mode Exit fullscreen mode

Code below is also important.
This useEffect will set the timer for the message, allowing it to disappear after 5000ms(5s).
Everytime isShow state changes, it will reset the timer to 5000ms.

 React.useEffect(() => {
    let timer: NodeJS.Timeout;
    if (isShow) {
      timer = setTimeout(() => {
        setIsShow(false);
      }, 5000);
    }
    return () => clearTimeout(timer);
  }, [isShow]);
Enter fullscreen mode Exit fullscreen mode

The ToastProvider returns a Provider component with four values: showToastMessage, isShow, text, and error.

 return <ToastContext.Provider value={{ showToastMessage, isShow, text, error }}>{children}</ToastContext.Provider>;
};
Enter fullscreen mode Exit fullscreen mode

Finally, we export a custom hook named useToast. This allows us to access the values provided by the ToastContext in any file that is wrapped by the provider, without having to define the context in each file.

export const useToast = () => {
  const context = useContext(ToastContext);
  if (context === undefined) {
    throw new Error('useToast must be used within a ToastProvider');
  }
  return context;
};
Enter fullscreen mode Exit fullscreen mode

2: Wrapping the App with the ToastContext.Provider

Then I wrap the whole app with ToastProvider and place PortalToasty component in app file.

export default function App({ Component, pageProps }: AppProps) {
  const { pathname } = useRouter();

  return (
    <ApolloProvider client={apolloClient}>
      <SessionProvider session={pageProps.session}>
        <ToastProvider>
              <Header />
              <div >
                <Component {...pageProps} />
              </div>
          <PortalToasty/> //place PortalToasty here
        </ToastProvider>  // wrap the whole app with Toast Provider
      </SessionProvider>
    </ApolloProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

3: Retrieve Values Using useToast Custom Hook in PortalToasty

In PortalToasty file, retrieve three global values using useToast custom hook to render the toast message conditionally.

const PortalToasty: React.FC = () => {
  const { isShow, text, error } = useToast();
Enter fullscreen mode Exit fullscreen mode

The Flow of Toast Message Display
1:addToadtMessage set the 'text' to whatever text value you pass to it and 'isShow' to true
2: PortalToasty is displayed since the isShow value is set to true.
3: After 5s, isShow is set to false, causing the ToastMessage to disappear.

Use Case

Here's a use case: After a router.push, I added the addToastMessage function to display a toast message:

  const { addToastMessage } = useToast();  //retrieve the addToastMessage function using useToadt hook
......


router.push(`/wardrobe/${userId}`);
          addToastMessage('Your piece has been successfully registered!');
Enter fullscreen mode Exit fullscreen mode

And Here Is the Result!!
result of the toast message

Conclusion

Personally, I prefer toast messages over large modal messages that require me to click a cancel button to close them. I'm really pleased that I finally took the time to create a custom toast message using useContext and React Portal. It's crucial to have access to the function that triggers the toast message in any file, as you'll likely find yourself using it in many places. That's just how frequently toast messages are used. I hope you find this post helpful!

Photo Credit
from:Unsplash
taken by: NASA

Top comments (0)