timolins / react-hot-toast

Smoking Hot React Notifications 🔥

Home Page:https://react-hot-toast.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Dismiss a headless toast?

julia-fix opened this issue · comments

I use headless for showing toasts that I need to be positioned differently from normal ones. And the issue is that there is no toast.dismiss for headless toast. What is the way to remove it?

Here is my code:

'use client';
import {  useToaster } from 'react-hot-toast/headless';
import style from './PositionedToast.module.scss';
import classNames from 'classnames';

export const PositionedToast = () => {
	const { toasts } = useToaster();

	return (
		<div
			style={{
				position: 'static',
			}}
		>
			{toasts.map((toast) => {
				const dismissToast = () => {
					// toast.dismiss(toast.id) is not a function
				};

				return (
					<div
						key={toast.id}
						className={classNames(style.toast, style[toast.type], toast.alignBottom && style.alignBottom)}
						style={{
							transition: 'all 0.5s ease-out',
							opacity: toast.visible ? 1 : 0,
							top: toast.position?.top,
							left: toast.position?.left,
							bottom: toast.position?.bottom,
							right: toast.position?.right,
						}}
						onClick={dismissToast}
						{...toast.ariaProps}
					>
						{toast.message}
					</div>
				);
			})}
		</div>
	);
};

To remove headless toasts without using toast.dismiss(), you can update the state of the toasts to remove the toast you want to dismiss. Since the toasts object is likely controlled by some state management system (such as React's state or a context), you can modify the state directly to remove the toast you want to dismiss.

export const PositionedToast = () => {
const { toasts, handlers } = useToaster();

const dismissToast = (id) => {
    handlers.dismiss(id); 
};

return (
    <div
        style={{
            position: 'static',
        }}
    >
        {toasts.map((toast) => (
            <div
                key={toast.id}
                className={classNames(style.toast, style[toast.type], toast.alignBottom && style.alignBottom)}
                style={{
                    transition: 'all 0.5s ease-out',
                    opacity: toast.visible ? 1 : 0,
                    top: toast.position?.top,
                    left: toast.position?.left,
                    bottom: toast.position?.bottom,
                    right: toast.position?.right,
                }}
                onClick={() => dismissToast(toast.id)}
                {...toast.ariaProps}
            >
                {toast.message}
            </div>
        ))}
    </div>
);

};

Try this i thought it should work

No, handlers does not have dismiss property. I ended up storing toast id in a local state and not rendering a toast if its id is in that array:

'use client';
import { useToaster } from 'react-hot-toast/headless';
import style from './PositionedToast.module.scss';
import classNames from 'classnames';
import { useState } from 'react';

export const PositionedToast = () => {
	const { toasts } = useToaster();
	const [deletedToasts, setDeletedToasts] = useState<string[]>([]);

	return (
		<div
			style={{
				position: 'static',
			}}
		>
			{toasts.map((toast) => {
				const dismissToast = () => {
					setDeletedToasts([...deletedToasts, toast.id]);
				};

				return deletedToasts.includes(toast.id) ? null : (
					<div
						key={toast.id}
						className={classNames(style.toast, style[toast.type], toast.alignBottom && style.alignBottom)}
						style={{
							transition: 'all 0.5s ease-out',
							opacity: toast.visible ? 1 : 0,
							top: toast.position?.top,
							left: toast.position?.left,
							bottom: toast.position?.bottom,
							right: toast.position?.right,
						}}
						onClick={dismissToast}
						{...toast.ariaProps}
					>
						{toast.message}
					</div>
				);
			})}
		</div>
	);
};

@julia-fix
As the handler object doesn't have the 'dismiss' method as of yet, you can manage it by shadowing the toast object.

Here is a example:

const useShadowToaster = () => {
  const { toasts } = useToaster();

  const [shadowToasts, setShadowToasts] = useState([]);
  const [deletedToastsId, setDeletedToastsId] = useState([]);

  useEffect(() => {
    const newToasts = toasts.filter(toast => !deletedToastsId.includes(toast.id));
    setShadowToasts(newToasts);
  }, [toasts, deletedToastsId]);

  const dismiss = (toastId) => {
    setDeletedToastsId(prev => [...prev, toastId]);
    setShadowToasts(prev => prev.filter(toast => toast.id !== toastId));
  };

  return {
    toasts: shadowToasts,
    handler: {
      dismiss,
    },
  };
};

It is possible to use toast.dismiss in headless mode. The problem in your example is that you are trying to call toast.dimiss on the toast instance, and not the global import { toast } from "react-hot-toast/headless .

I usually use the variable name t when mapping over toasts to avoid this naming conflict. Here is a working demo.

Alternatively, you can rename the global toast object when importing to avoid the conflict:

import { toast as globalToast } from "react-hot-toast/headless"

globalToast.dismiss(id)