timolins / react-hot-toast

Smoking Hot React Notifications 🔥
https://react-hot-toast.com
MIT License
9.85k stars 331 forks source link

Dismiss a headless toast? #344

Closed julia-fix closed 7 months ago

julia-fix commented 9 months ago

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>
    );
};
Sumit-Kumar-0 commented 9 months ago

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

julia-fix commented 9 months ago

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>
    );
};
mekanhaji commented 9 months ago

@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,
    },
  };
};
timolins commented 7 months ago

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)