【React.js】Making a Simple Toast Message Component with useContext and React Portal　-part3-
Published: 2024-03-10 22:17:08

**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

```javascript

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;
};
```

Create a context in the code below.
```javascript
const ToastContext = React.createContext<ToastContextState | undefined>(undefined);
```

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

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."

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

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.

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


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

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

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.

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

## 2: Wrapping the App with the ToastContext.Provider

Then I wrap the whole app with `ToastProvider` and place `PortalToasty` component in app file.
```javascript
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>
  );
}
```

## 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.

```javascript
const PortalToasty: React.FC = () => {
  const { isShow, text, error } = useToast();
```

**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!');
```

**And Here Is the Result!!**
![result of the toast message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/anv75q4mzm5mudcry4ue.png)

### 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:<a href="https://unsplash.com/ja/%E5%86%99%E7%9C%9F/%E5%AE%87%E5%AE%99%E7%A9%BA%E9%96%93%E3%81%AE%E5%86%99%E7%9C%9F-Q1p7bh3SHj8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
taken by: <a href="https://unsplash.com/ja/@nasa?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">NASA</a>