A generic ToastComponent

Hi

I want to have a generic ToastComponent so I could just send it the content and it will show itself. At the moment I did the following:
Created a component
export default function ToastAlert(props) {

    const toastRef = useRef(null);

    useEffect(() => {
        if (props.content === "") return;
        toastRef.current.show();
    });

    return (
        <ToastComponent title="title" content={props.content} ref={toastRef} showCloseButton={true} newestOnTop={true} position={{X: "Right", Y: "Bottom"}} cssClass="e-toast-info" open={props.initContent}/>
    )
}

Added it to a page I want it to show
<ToastAlert content={toastContent} initContent={onOpenToast}/>
I also had to add the following in order to initialize the content, otherwise, it wouldn't show if the same message needed to be shown again (see for more info):
const onOpenToast = () => {
    setToastContent("");
}


When I want to show it, I do
if (x) setToastContent("Content 1");
else setToastContent("Content 2");

Generally, this is working but
1. I have the add the toast code to every page.
2. I tried return <ToastAlert... /> instead of setting the state of the content but the component didn't render itself and was not visible.
3. If the same message needed to show again, meaning the state didn't change, the component didn't render itself. For that to work, I needed to clear the content everytime (see onOpenToast event above).

Is there a better/cleaner way to achieve what I need?
Thanks

11 Replies 1 reply marked as answer

AM Amos April 25, 2021 03:32 PM UTC

Also, if I want to show a Toast in an axios interceptor, which is not a component
apiClient.interceptors.response.use(response => response, error => {
    if (error.message.toString().includes("Network Error") || error.message.toString().includes("status code 500")) {
        return <ToastAlert content="Oops, something happened" />
    } else if (error.response.data !== "")
        return <ToastAlert content={error.response.data} />;
    else return <ToastAlert content={error.message} />;
    throw error;
})

but the above obviously doesn't work. What can be done?
Thanks


IS Indrajith Srinivasan Syncfusion Team April 26, 2021 12:07 PM UTC

Hi Amos, 
 
Greetings from Syncfusion support, 
 
We have validated your reported query. We suggest you to call the toast show method with a minimal delay, to show the toast with the updated state changes. We have also prepared a sample that tries to meet your requirements. 
 
In the below sample the following are configured, 
  • Toast rendered in a separate page parent.js.
  • Click the button “Show Toast” to show the toast with initial content.
  • Click the button “Change Toast Content” to show the toast with updated content.
  • The toast content is dynamically changed using the react setState.
 
Can you please check the above sample and let us know if it meets your requirements? 
 
Regards, 
Indrajith 


Marked as answer

AM Amos April 26, 2021 09:10 PM UTC

Hi

Not quite, notice that I'm talking about a separation between the Toast code and the activation/showing of it.
It's like the DialogUtility you have, where I can define it once with parameters and I can call it from everywhere in the app:

import {DialogUtility} from "@syncfusion/ej2-popups";
import "./alertMessages.css";

const showAlert = (contentMessage, onOKClick) => {
    DialogUtility.alert({
        animationSettings: { effect: 'FadeZoom' },
        content: contentMessage,
        okButton: { text: 'OK', click: onOKClick },
        position: { x: 'center', y: 'center' },
        title: "title",
    });
}

export default showAlert;

So I'm talking about something similar, but with a toast
Thanks


RK Revanth Krishnan Syncfusion Team April 27, 2021 12:25 PM UTC

Hi Amos, 
 
We are currently validating your query, we will update you with further details on or before 29th April. 
 
We appreciate your patience till then. 
 
Regards, 
Revanth 



AM Amos April 29, 2021 10:19 PM UTC

Just a reminder so it won't get lost :)


RK Revanth Krishnan Syncfusion Team April 30, 2021 03:20 AM UTC

Hi Amos, 
 
 
Sorry for the delay. 
 
 
We have further validated your query, and the toast cannot be rendered the same as the Dialog utility. But the different toast can be show using Toast Model in the show method, where all the properties can be defined in the show method. We have prepared a sample for your reference, 
 
Code Snippet: 
 
export class Default extends SampleBase { 
  constructor() { 
    super(...arguments); 
    this.toasts = [ 
      { 
        title: "Warning !", 
        content: "There was a problem with your network connection.", 
        cssClass: "e-toast-warning" 
      }, 
      { 
        title: "Success !", 
        content: "Your message has been sent successfully.", 
        cssClass: "e-toast-success" 
      }, 
      { 
        title: "Error !", 
        content: "A problem has been occurred while submitting your data.", 
        cssClass: "e-toast-danger" 
      }, 
      { 
        title: "Information !", 
        content: "Please read the comments carefully.", 
        cssClass: "e-toast-info" 
      } 
    ]; 
    this.timeOutDelay = 600; 
    this.toastFlag = 0; 
    this.position = { X: "Right", Y: "Bottom" }; 
  } 
  toastCreated() { 
    this.toastInstance.show(this.toasts[this.toastFlag]); 
    ++this.toastFlag; 
  } 
  toastShow() { 
    setTimeout(() => { 
      this.toastInstance.show(this.toasts[this.toastFlag]); 
      ++this.toastFlag; 
      if (this.toastFlag === this.toasts.length) { 
        this.toastFlag = 0; 
      } 
    }, this.timeOutDelay); 
  } 
  btnClick() { 
    this.toastShow(); 
  } 
  render() { 
    return ( 
      <div> 
        <ButtonComponent 
          cssClass="e-primary" 
          onClick={(this.btnClick = this.btnClick.bind(this))} 
        > 
          {" "} 
          Show Toast{" "} 
        </ButtonComponent> 
        <ToastComponent 
          ref={toast => (this.toastInstance = toast)} 
          position={this.position} 
          created={(this.toastCreated = this.toastCreated.bind(this))} 
        /> 
      </div> 
    ); 
  } 
} 
 
this.toastInstance.show({ 
    title: "First Toast Created", 
    content: "First Toast content", 
    cssClass: "e-toast-warning" 
  } 
); 
Note: Each time the toast can be called with different properties using the Toast model in show method. 
 
 
Please check the above code snippet and the samples and let us know if it comes close to your requirement. 
 
Regards, 
Revanth 



AM Amos April 30, 2021 10:33 AM UTC

This is again when the toast is in the same page as its activator but I combined my solution with yours.

On each page I defined a ToastComponent wrapper and gave it a toastRef and "linked" it as the ref of the ToastComponent itself (props.toastRef)
When wanting to show a toast I call a generic function that receives the ref and a message and I do toastRef.current.show({...});

it works but I'm forced to add a ToastComponent wrapper on each page + a ref to it.
Can you consider implementing a generic Toast component similar to DialogUtility in a future version?


AM Amos May 1, 2021 09:52 AM UTC

I encountered a new problem when doing the above

When I route to a different location/url and a Toast is not shown - it works.
When I route to a different location/url and a Toast is shown, the following error occurs
NotFoundError: Node.removeChild: The node to be removed is not a child of this node

this is my code

        apiClient.get("...").then(response => {
            if (!isEmptyObject(response.data.result))
                dispatch({...});
            apiClient.get("...").then(response1 => {
                if (response1.data) history.push("/Overview"); //<<<<<<<<<<<<<<<<<                else history.push("/NewUser");
            }).catch(e => { handleAxiosCatch(e, toastRef)});
        }).catch(e => { handleAxiosCatch(e, toastRef)});

This is the failed code behind the scenes:
Router/_this.unlisten<
modules/Router.js:34
  31 | if (!props.staticContext) {
32 | this.unlisten = props.history.listen(location => {
33 | if (this._isMounted) {
> 34 | this.setState({ location });
| ^ 35 | } else {
36 | this._pendingLocation = location;
37 | }
Maybe it's connected to how I implemented the ToastComponent but the error happens behind the scenes on the router code and I'm not sure what is causing the failure.
Any suggestions?

From debugging I see that when it navigates to the new page, it tries to remove the div of the toast but since it belongs to the previous page, it fails for the mentioned error. I tried adding toastRef.destroy() before pushing the new url but it didn't help.

At first, it was like that and it fails.
<>


           
....
           



I then moved the ToastAlert section inside the Card section
<>

           
                     
....
           


and now it works, I know you didn't develop React but if you can give me any hint on why this change made it work, I'd appreciate it, thanks.


RK Revanth Krishnan Syncfusion Team May 3, 2021 12:43 PM UTC

Hi Amos, 
 
 
Good day to you. 
 
 
We have further validated your queries, 
 
Query 1: “It works but I'm forced to add a Toast Component wrapper on each page + a ref to it. Can you consider implementing a generic Toast component similar to ‘DialogUtility’ in a future version?” 
 
We have validated your query and have considered “Provide support to render Toast component as utility function” as a feature request from our end and logged the report for the same and the fix will be included with any of our upcoming releases. 
   
You can now track the current status of the report, review the proposed resolution timeline, and contact us for any further inquiries through this link: https://www.syncfusion.com/feedback/24946 
 
Query 2: “When I route to a different location/URL and a Toast is not shown - it works. When I route to a different location/URL and a Toast is shown, the following error occurs ‘NotFoundError: Node.removeChild: The node to be removed is not a child of this node’” 
 
We tried to reproduce the issue with the shared code snippet by making the issue reproducible, but we couldn’t reproduce the issue from our end. 
 
Can you please share us with the following details? 
  • The code snippet of the pages where all the toast is rendered and shown in the application.
  • The exact package version of the component used in the application.
  • Please share us with the issue reproducible sample.
 
The above details will be helpful for us to validate and reproduce the issue and assist you at the earliest. 
 
Regards, 
Revanth 



AM Amos May 3, 2021 12:53 PM UTC

query 2: the code didn't come out right in my previous comment but all I did was putting the <ToastAlert.../> in a different location of the html code and it worked. Instead of outside of the surrounding div, it was inside of it. It looks like something related to how React works.


RK Revanth Krishnan Syncfusion Team May 4, 2021 06:10 AM UTC

Hi Amos, 
 
Thanks for the update. 
 
We are glad that the issue has been resolved. Please let us know if you need any further assistance. 
 
Regards, 
Revanth 


Loader.
Up arrow icon