timolins / react-hot-toast

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

How to access toasts state from outside of React function component? #296

Open micah-redwood opened 1 year ago

micah-redwood commented 1 year ago

Hi, first off thanks for maintaining this really nifty, well-designed library!

I recently ran into a use case where some code outside of a React component needed to know if a particular toast was visible.

AFAIK the only way to access all the toasts' state is via the useToasterStore hook, so to expose the list of visible toast IDs to the outside, I had to manually render a component and expose it's state via a wrapping Promise:

import { Fragment } from 'react';
import ReactDOM from 'react-dom/client';
import toast, { ToastPosition, useToasterStore } from 'react-hot-toast';
import waitForTimers from 'wait-for-timers';
import { reportSentryError } from './sentry';

// Very hacky way to get toast ids by temporarily rendering component to use hook, but it works
export function getToastIds(): Promise<string[]> {
  return new Promise((resolve) => {
    // Should never take longer than a few ms
    const cancelTimeout = waitForTimers([10], () => {
      reportSentryError('getToastIds render too slow');
      resolve([]);
    });

    // Temporarily render component to call custom hook & get toast ids
    const root = ReactDOM.createRoot(document.createElement('div'));
    root.render(
      <ToasterStoreComponent
        setIds={(ids: string[]) => {
          cancelTimeout();
          resolve(ids);
        }}
      />
    );
  });
}

// Only used to call custom hook, doesn't render anything
function ToasterStoreComponent({ setIds }: { setIds: (result: string[]) => void }) {
  const { toasts } = useToasterStore();
  setIds(toasts.map((toast) => toast.id));
  return <Fragment />;
}

I do not love this solution for many reasons, and today discovered that occasionally it takes over 10ms for this render to happen, slowing down my app.

Any chance we could expose the return value of useToasterStore or allow registering a custom listener? Really any way of accessing this data without having to trigger a React function component render.

Thanks!

kaliaboi commented 1 year ago

Hi, as far as I understand your question correctly – you need a separate component to know when a specific toast is in the DOM. Looking at the API, the way I would do this is by:

  1. Setting up custom toast component using the useToast() hook.
  2. Attaching a ref to the component.
  3. Using the ref to see if the component is in the DOM currently by adding it to some sort of global context.

Hope this helps.