fkhadra / react-toastify

React notification made easy 🚀 !
https://fkhadra.github.io/react-toastify/introduction
MIT License
12.58k stars 692 forks source link

React Suspense and react-toastify #276

Closed goenning closed 5 years ago

goenning commented 5 years ago

Is there any way to use react-toastify with React.lazy?

My scenario is that there are very few places we show the toaster, probably only 5% of the users ever see a toaster. As of now, we push react-toastify on the main bundle, forcing everyone to download it.

I'm thinking on how can we use React.lazy to solve this, but I can't don't know how would I do it, mostly because the toast function requires to be on the DOM when it's called.

Any thoughts on how to achieve this?

Thanks!

fkhadra commented 5 years ago

Hey @goenning,

I made an example with React.lazy and Suspense Edit 2nmnvrn6r

Now let me give you more details. When you call for instance toast('Hello'), the notification is added to a queue. So you can even call toast('Hello') several time without mounting the ToastContainer. Then, when the ToastContainer is mounted later all the queued notifications will be displayed.

Is that what you are looking for ?

goenning commented 5 years ago

Very close, but not exactly.

In your example the bundle that contains ToastContainer will be loaded async, which is a good start, but it'll still be loaded and mounted even when there was no toast('Hello') call.

What I'm trying to achieve is: 1) some component will call toast('Hello') and internally it'll add the notification to the queue 2) trigger SOMETHING that will download the bundle that contains ToastContainer, wait for it to load and mount it to the DOM 3) whenever mounted, show notification

I think this package has 1 & 3 builtin. Number 2 is achieved with Suspense and Lazy, but how to trigger that?

fkhadra commented 5 years ago

@goenning so you want to mount the ToastContainer only if toast('hello') is called. If toast is not called no need to mount the container right ?

goenning commented 5 years ago

Exactly!

fkhadra commented 5 years ago

I updated the sample using setState Edit 2nmnvrn6r

For a real application you may need to do that at several place. If you are using a state management library like mobx or redux it will be even easier to implement. For instance with mobx one could do:

class UiStore {
  hasContainer = false;

  notify(content, options){
    if(! this.hasContainer){
        this.hasContainer = true;
    }
    toast(content, options); 
  }
}
goenning commented 5 years ago

Interesting. Something just came to my mind now. To achieve code split, we need to wrap both toast and ToastContainer on an async module. Because if we import "react-toastify" on any other component (like App.js on your example), webpack will bundle it as sync bundle, which defeats the purpose of code splitting.

But you gave me some ideas, I'll try it out, see how it goes and update this thread. Thanks!

fkhadra commented 5 years ago

You're welcome!

goenning commented 5 years ago

For anyone interested: https://github.com/goenning/example-react-toastify-code-split

This project will only load react-toastify library when the user clicks on the logo for the first time.

If react-toastify supported React Portals, the solution would be slightly simpler, as we wouldn't need to mount the ToastContainer. Maybe that could be a new feature request? 😄

fkhadra commented 5 years ago

Hey @goenning,

How will React.portal could make things simpler? Even with the portal the ToastContainer will be mounted sooner or later. Am I missing something?

I would love to hear more about your idea.

goenning commented 5 years ago

Well, I don't know much about react-toastify internals, but as far as I understood,toast function queues the notifications and ToastContainer consumes them and renders it as a child.

If renderTo was added to toast options, like:

toast("Hello World", {
  // ...options
  renderTo: "root-toastify"
}

toast could then check if renderTo property is defined, if so, then it wouldn't queue the notification, but actually append it to the DOM node with id root-toastify using React Portals, thus completely bypassing the ToastContainer.

I haven't done much with React Portals, but I guess that's what it is for.

fkhadra commented 5 years ago

@goenning I really like the idea of having a renderTo option. This open a lot of possibility like having multiple container which is not supported for the moment.

This also allow to render the ToastContainer only when needed, which is what you are looking for.

I see one downside with this approach. Some props are only available for the ToastContainer and we will lose the ability to define them. So far, only 2 props are concerned: rtl andnewestOnTop. Anyway this can be solved easily

Thank you for the brilliant idea(renderTo)! I'll try to play around with it.

goenning commented 5 years ago

Just for reference, I have implemented it on a real app and it works 💯

kapture 2018-11-24 at 10 28 27

This is how we did it https://github.com/getfider/fider/pull/645

fkhadra commented 5 years ago

This is very neat. I'll probably start to work on the next release in 2 weeks. I'll leave the issue open with a label how to. Thank you for sharing your solution

fkhadra commented 5 years ago

Hey @goenning,

For the next release the ToastContainer will be optional. The container will be mounted on demand only.