fkhadra / react-toastify

React notification made easy 🚀 !
https://fkhadra.github.io/react-toastify/introduction
MIT License
12.74k stars 700 forks source link

Not processing toast.promises in the background with low limit of ToastContainer #698

Open Elugormo opened 2 years ago

Elugormo commented 2 years ago

The bug is related to the incorrect processing of promises. In my case, I have to queue a lot of notifications that are tightly dependent on network requests, that may take some time to process. Currently, for some reason, after queueing more promises that are available in the set limit of ToastContainer we get a forever pending promise, that can't somehow be resolved. FYI I can't increase the limit of visible toasts. I have provided a sandbox in case someone doesn't understand the issue. So when the limit is 3, and you try to click the 'Notify' button multiple times (for example 10), after resolving 3 first promises, other ones will be left in a pending state forever.

https://codesandbox.io/s/lingering-cookies-3545w?file=/src/index.js

The expected behavior is in resolving all promises correctly, without any left in pending state.

villqrd commented 2 years ago

Hello!

Same issue here, did you find a solution by any chance?

Elugormo commented 2 years ago

You might try to set a unique Id to the toast, so this problem won't appear if multiple requests for the same operation are coming, as shown in my Sandbox. By setting { toastId: 'something' } you will omit the problem for obvious reasons - only one promise will be handled. But in case there are a lot of them and they are different, this is impossible due to the specifics of the mechanisms that are used inside the library. I haven't found a trivial solution to this one by inspecting the code.

villqrd commented 2 years ago

@Elugormo Thanks for your answer, it's a bit helping although still an issue..

demming commented 1 year ago

It appears to work ordinarily if you pass any updateId on Promise toast invocation (perhaps in conjunction with a toastId), but this in turn will violate the limit property, whereby each appearing Promise toast will push the other toasts beyond that threshold.

tsolutionsx commented 4 months ago

I think it will help you.

I just customized toast for my project like this. (this is just simple component, so you need to re-customize component based on your project.)


import { Bounce, Id, ToastOptions, toast } from "react-toastify";
// Notification Utility
const toastIds: Record<string, Id> = {};

export const alert = (
  label: string,
  type: "info" | "success" | "warning" | "error" | "default" = "success",
  mode: "light" | "dark" = "dark"
): void => {
  if (toastIds[label] && toast.isActive(toastIds[label])) {
    return;
  }

  const options: ToastOptions = {
    position: "top-center",
    autoClose: 5000,
    hideProgressBar: false,
    closeOnClick: true,
    pauseOnHover: true,
    draggable: true,
    progress: undefined,
    theme: mode,
    transition: Bounce
  };

  const toastFunc = {
    info: toast.info,
    success: toast.success,
    warning: toast.warn,
    error: toast.error,
    default: toast
  };

  toastIds[label] = toastFunc[type](label, options);
};

in this case, even if you click several times toast event, it will show only one toast. maybe it can be a way to solve this problem.

thanks

tsolutionsx commented 4 months ago

Or other way to keep 3 alert as like you wanted.

import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

const App = () => {
  const [queue, setQueue] = useState([]);
  const limit = 3;

  const addToQueue = (promise, resolve, reject) => {
    setQueue((prevQueue) => [
      ...prevQueue,
      { promise, resolve, reject }
    ]);
  };

  const processQueue = useCallback(() => {
    setQueue((prevQueue) => {
      const activePromises = prevQueue.filter((item) => item.promise.status === 'pending');
      const availableSlots = limit - activePromises.length;

      if (availableSlots > 0) {
        const nextPromises = prevQueue.filter((item) => item.promise.status === 'queued').slice(0, availableSlots);

        nextPromises.forEach((item) => {
          item.promise.status = 'pending';
          item.promise
            .then(() => {
              item.resolve();
              processQueue();
            })
            .catch(() => {
              item.reject();
              processQueue();
            });
        });
      }

      return prevQueue.filter((item) => item.promise.status !== 'resolved' && item.promise.status !== 'rejected');
    });
  }, [limit]);

  const handleNotify = () => {
    const promise = new Promise((resolve, reject) => {
      addToQueue(promise, resolve, reject);
      processQueue();
    });

    promise.status = 'queued';

    toast.promise(
      promise,
      {
        pending: "Promise is pending",
        success: "Promise resolved",
        error: "Promise rejected"
      }
    );

    setTimeout(() => {
      promise.status = 'resolved';
      promise.then(() => promise.resolve());
    }, Math.random() * 3000 + 1000);
  };

  return (
    <div>
      <button onClick={handleNotify}>Notify</button>
      <ToastContainer limit={limit} />
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);