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

The incompatibility of different Toaster APIs (hooks vs singleton) #15

Open
icekimi23 opened this issue Jul 2, 2024 · 5 comments
Open
Labels
enhancement New feature or request

Comments

@icekimi23
Copy link

Objective

In the gravity-ui library, there are several ways to invoke a toast (a notification window at the edge of the screen): through the useToaster hook or, where hooks are not applicable, through the Toaster singleton.

However, when using the singleton approach, a problem may arise where components rendered within such a toast do not have access to various application providers, because these toasts are mounted in a different root.

Using both approaches in a single project can lead to issues with toasts overlapping each other, as each method has its own stack of toasts. (An example is shown in the video.)

Solution Proposal

Create a universal API with methods that do not conflict with each other. This could be implemented similar to Redux, where a singleton is passed into a provider and can also be used externally.

Rough implementation

Click to expand
import React, { createContext, useContext, useState, useMemo, useEffect } from "react";

export const ToasterContext = createContext(null);
ToasterContext.displayName = "ToasterContext";

export const ToasterProvider = ({ toaster, children }) => {
  const [toasts, setToasts] = useState([]);

  useEffect(() => {
    const updateToasts = (toasts) => {
      setToasts((prev) => [...toasts]);
    };

    toaster.on(updateToasts);

    return () => {
      toaster.off(updateToasts);
    };
  }, [toaster]);

  return (
    <ToasterContext.Provider value={toaster}>
      <>
        {children}
        <div>
          {toasts.map((toast) => (
            <div>{toast}</div>
          ))}
        </div>
      </>
    </ToasterContext.Provider>
  );
};

export const useToaster = () => {
  const toaster = useContext(ToasterContext);

  return useMemo(() => toaster, [toaster]);
};

class Toaster {
  constructor() {
    this.toasts = [];
    this.listeners = [];
  }

  add(toast) {
    this.toasts.push(toast);

    for (const listener of this.listeners) {
      listener(this.toasts);
    }
  }

  on(fn) {
    this.listeners.push(fn);
  }

  off(fn) {
    this.listeners = this.listeners.filter((listener) => listener !== fn);
  }
}

const toaster = new Toaster();

function SomeComponent() {
  const toasterFromHook = useToaster();

  const handleClick = () => {
    toasterFromHook.add("a new fresh toast");
  };

  return <button onClick={handleClick}>Add toast</button>;
}

setTimeout(() => {
  toaster.add('toast from setTimeout');  
}, 3000);

export default function App() {
  return (
    <div className="App">
      <ToasterProvider toaster={toaster}>
        <SomeComponent />
      </ToasterProvider>
    </div>
  );
}

The main idea is to create an object that maintains the state of toasts and allows subscribing to its changes. Essentially, it's the same concept as redux + react-redux. This way, the object is created once in the service and then used as needed.

This is my initial thought, but if someone suggests something simpler, I'd be happy to take a look :)

Definition of done

A pull request is made with a universal, non-conflicting API.

@korvin89 korvin89 added the enhancement New feature or request label Jul 2, 2024
@korvin89
Copy link

korvin89 commented Jul 2, 2024

cc @ValeraS @ogonkov

@resure resure moved this to Discussion in Gravity RFC Aug 7, 2024
@SeqviriouM
Copy link

@ValeraS @ogonkov what do you think?

@ogonkov
Copy link

ogonkov commented Aug 15, 2024

We decided give it a shot

@SeqviriouM
Copy link

@icekimi23 @ogonkov are you ready to pick up the task and offer the proposal?

@icekimi23
Copy link
Author

Created PR gravity-ui/uikit#1987

@SeqviriouM SeqviriouM moved this from Discussion to In Progress in Gravity RFC Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: In Progress
Development

No branches or pull requests

4 participants