Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicated Notifications by Race Condition #946

Closed
H4ad opened this issue Apr 14, 2023 · 3 comments
Closed

Duplicated Notifications by Race Condition #946

H4ad opened this issue Apr 14, 2023 · 3 comments

Comments

@H4ad
Copy link

H4ad commented Apr 14, 2023

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

I added just once the notification but it has been displayed twice.

From what I looked, the problem is with these lines:

useEffect(() => {
instance.containerId = props.containerId;
eventManager
.cancelEmit(Event.WillUnmount)
.on(Event.Show, buildToast)
.on(Event.Clear, toastId => containerRef.current && removeToast(toastId))
.on(Event.ClearWaitingQueue, clearWaitingQueue)
.emit(Event.DidMount, instance);
return () => {
toastToRender.clear();
eventManager.emit(Event.WillUnmount, instance);
};
}, []);

Because I'm using StrictMode, this function is called twice but WillMount is not properly cleaned because we call cancelEmit, which makes the cleanup function never be called since we call events using setTimeout:

.on(Event.WillUnmount, (containerInstance: ContainerInstance) => {
containers.delete(containerInstance.containerId || containerInstance);
if (containers.size === 0) {
eventManager
.off(Event.Show)
.off(Event.Clear)
.off(Event.ClearWaitingQueue);
}
});

If the toast is called very close to the last notification, react will join these two calls in a single batch (my guess), and then, the buildToast function cannot verify if the toast is duplicated because it was not added yet to the toastToRender by appendToast.

// not handling limit + delay by design. Waiting for user feedback first
if (
props.limit &&
props.limit > 0 &&
instance.count > props.limit &&
isNotAnUpdate
) {
instance.queue.push({ toastContent, toastProps, staleId });
} else if (isNum(delay)) {
setTimeout(() => {
appendToast(toastContent, toastProps, staleId);
}, delay);
} else {
appendToast(toastContent, toastProps, staleId);
}
}

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.

https://codesandbox.io/s/amazing-cray-9f19y2

What is the expected behavior?

To not duplicate the notifications.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

  • React 18.2.0
  • React Toastify Latest Version.
  • Chrome Latest Version.
@H4ad
Copy link
Author

H4ad commented Apr 14, 2023

I could fix locally by adding another safe guard condition on buildToast on this line:

if (!canBeRendered(content) || isNotValid(options) || instance.queue.some(t => t.toastProps.toastId === options.toastId)) return;

@gudlyf
Copy link

gudlyf commented May 9, 2023

I believe I had a similar issue and resolved it by setting the toastId:

toast.success('Success message here', {
  toastId: new Date().getTime().toString() + Math.random()
});

@fkhadra fkhadra mentioned this issue Jun 11, 2023
@fkhadra
Copy link
Owner

fkhadra commented Nov 1, 2023

I'll close the issue given it can be solved using a custom id. In v10 it won't happen as the library internal implementation has switched to useSyncExternalStore

@fkhadra fkhadra closed this as completed Nov 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants