-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Add OverlayToaster.createAsync
method to support React 18
#6599
Conversation
OverlayToaster.createAsync
method to support React 18
fcf7105
to
cbe7102
Compare
|
||
afterEach(() => { | ||
toaster.clear(); | ||
ReactDOM.unmountComponentAtNode(testsContainerElement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were previously calling ReactDOM.unmountComponentAtNode
on the wrong DOM element. The OverlayToaster.create()
method creates a new <div>
within testsContainerElement
to render on.
toaster = spec.create({ maxToasts: 3 }, testsContainerElement); | ||
}); | ||
|
||
after(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the additional cleanup in a few tests that call OverlayToaster.create()
directly (instead of using the existing Toaster
instance), I was seeing the "does not attach toast container to body on script load" test fail.
@@ -30,7 +30,14 @@ horizontally aligned along the left edge, center, or right edge of its container | |||
|
|||
There are three ways to use __OverlayToaster__: | |||
|
|||
1. __Recommended__: use the `OverlayToaster.create()` static method to create a new `Toaster` instance: | |||
1. __Recommended__: use the `OverlayToaster.createAsync()` static method to create a new `Toaster` instance: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would actually swap the recommended approach here since OverlayToaster.create()
and OverlayToaster.createAsync()
both leak memory and add a DOM node to the page forever. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sure, I'm down with that, we can change the recommended approach. So we'd reverse the order of approaches: 3 becomes 1 (recommended), and 1 becomes 3. Since it seems like most people prefer having an imperative API in their application to trigger toasts rather than storing a list of Toasts in app state and rendering <Toast>
themselves.
It's worth adding more docs under the "Static usage" section around L105 with your note about memory leaks and leaving a DOM node on the page forever. Also perhaps update the code snippets there to use createAsync
instead of create
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sure, I'm down with that, we can change the recommended approach. So we'd reverse the order of approaches: 3 becomes 1 (recommended), and 1 becomes 3. Since it seems like most people prefer having an imperative API in their application to trigger toasts rather than storing a list of Toasts in app state and rendering
<Toast>
themselves.
I pushed up a few commits that switched 1 and 3, but decided to walk this back. The primary factor that changed my mind was that it becomes harder to pass the OverlayToaster
ref down the React component tree. I think that will be a bigger point of confusion than I originally thought when I suggested switching the recommendations. Thanks for being open to my suggestion.
The latest push keeps the current recommendation since it may be the best of all options, but adds more notes around only creating one Toaster
instance.
It's worth adding more docs under the "Static usage" section around L105 with your note about memory leaks and leaving a DOM node on the page forever.
Can do! Added in d375696.
Also perhaps update the code snippets there to use
createAsync
instead ofcreate
?
Good suggestion. Pushed up a commit that switches the docs and code snippets to createAsync
here: 9b1fb93
Add OverlayToaster.createAsync testBuild artifact links for this commit: documentation | landing | table | demoThis is an automated comment from the deploy-preview CircleCI job. |
cbe7102
to
df42ba8
Compare
Add OverlayToaster.createAsync testBuild artifact links for this commit: documentation | landing | table | demoThis is an automated comment from the deploy-preview CircleCI job. |
df42ba8
to
32af487
Compare
Add OverlayToaster.createAsync testBuild artifact links for this commit: documentation | landing | table | demoThis is an automated comment from the deploy-preview CircleCI job. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for improving the test suite, nice to see the amount of code re-use there 👍🏽
code changes & behavior look good, I'm requesting some docs changes in this review
would you mind adding a new (small) docs example using OverlayToaster.createAsync()
which gets shown in this part of the docs? it doesn't need to have options:
this will help us to have a quick & easy way to test the imperative API interactively in the docs preview. Currently, toastExample.tsx
only uses the declarative API.
@@ -30,7 +30,14 @@ horizontally aligned along the left edge, center, or right edge of its container | |||
|
|||
There are three ways to use __OverlayToaster__: | |||
|
|||
1. __Recommended__: use the `OverlayToaster.create()` static method to create a new `Toaster` instance: | |||
1. __Recommended__: use the `OverlayToaster.createAsync()` static method to create a new `Toaster` instance: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sure, I'm down with that, we can change the recommended approach. So we'd reverse the order of approaches: 3 becomes 1 (recommended), and 1 becomes 3. Since it seems like most people prefer having an imperative API in their application to trigger toasts rather than storing a list of Toasts in app state and rendering <Toast>
themselves.
It's worth adding more docs under the "Static usage" section around L105 with your note about memory leaks and leaving a DOM node on the page forever. Also perhaps update the code snippets there to use createAsync
instead of create
?
packages/core/src/common/errors.ts
Outdated
export const TOASTER_CREATE_ASYNC_NULL = | ||
ns + | ||
` OverlayToaster.createAsync() received a null component ref. This can happen if called inside React lifecycle ` + | ||
`methods in React 16. See usage example on the docs site.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: which documentation site? Blueprint or React? Might be useful to just add a link
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with it being useful to just add a link. This message was copied from the TOASTER_CREATE_NULL
. I'll update the original message as well. 4779ba2
Thanks for all the feedback! Agree that the docs need to have some improvements. I'm still working through a few more clarifications and will move the PR to draft mode until I wrap those up. |
Move "React component usage example" further upBuild artifact links for this commit: documentation | landing | table | demoThis is an automated comment from the deploy-preview CircleCI job. |
This allows a future commit to add a new spec for OverlayToaster.createAsync and reuse the tests written in this file.
8f001a5
to
394a7c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would you mind adding a new (small) docs example using OverlayToaster.createAsync() which gets shown in this part of the docs? it doesn't need to have options
Can do! Done in commit 9bdf637.
I ended up moving this to the Example section since the code snippets were very similar to what the interactive example implements. If that's not a good place, I can move it back to where you suggested.
packages/core/src/common/errors.ts
Outdated
export const TOASTER_CREATE_ASYNC_NULL = | ||
ns + | ||
` OverlayToaster.createAsync() received a null component ref. This can happen if called inside React lifecycle ` + | ||
`methods in React 16. See usage example on the docs site.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with it being useful to just add a link. This message was copied from the TOASTER_CREATE_NULL
. I'll update the original message as well. 4779ba2
@@ -30,7 +30,14 @@ horizontally aligned along the left edge, center, or right edge of its container | |||
|
|||
There are three ways to use __OverlayToaster__: | |||
|
|||
1. __Recommended__: use the `OverlayToaster.create()` static method to create a new `Toaster` instance: | |||
1. __Recommended__: use the `OverlayToaster.createAsync()` static method to create a new `Toaster` instance: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sure, I'm down with that, we can change the recommended approach. So we'd reverse the order of approaches: 3 becomes 1 (recommended), and 1 becomes 3. Since it seems like most people prefer having an imperative API in their application to trigger toasts rather than storing a list of Toasts in app state and rendering
<Toast>
themselves.
I pushed up a few commits that switched 1 and 3, but decided to walk this back. The primary factor that changed my mind was that it becomes harder to pass the OverlayToaster
ref down the React component tree. I think that will be a bigger point of confusion than I originally thought when I suggested switching the recommendations. Thanks for being open to my suggestion.
The latest push keeps the current recommendation since it may be the best of all options, but adds more notes around only creating one Toaster
instance.
It's worth adding more docs under the "Static usage" section around L105 with your note about memory leaks and leaving a DOM node on the page forever.
Can do! Added in d375696.
Also perhaps update the code snippets there to use
createAsync
instead ofcreate
?
Good suggestion. Pushed up a commit that switches the docs and code snippets to createAsync
here: 9b1fb93
This should be ready for another review after the holidays. I wanted to finish my in-progress changes before I lost context over the next few weeks. Please feel free to ignore this until next year. Thanks! |
Add React 18 domRenderer exampleBuild artifact links for this commit: documentation | landing | table | demoThis is an automated comment from the deploy-preview CircleCI job. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<div class="@ns-callout @ns-intent-primary @ns-icon-info-sign @ns-callout-has-body-content"> | ||
<h5 class="@ns-heading">Beware of memory leaks</h5> | ||
|
||
The static `createAsync` and `create` methods create a new `OverlayToaster` instance for the full lifetime of your | ||
application. Since there's no React parent component, these methods create a new DOM node as a container for the | ||
rendered `<OverlayToaster>` component. Every `createAsync` call will add a new DOM node. We do not recommend creating a | ||
new `Toaster` every time a toast needs to be shown. To minimize leaking: | ||
|
||
1. Call `OverlayToaster.createAsync` once in an application and [share the instance](#core/components/toast.example). | ||
2. Consider one of the alternative APIs that mount the `<OverlayToaster>` somewhere in the application's React component tree. This provides component lifecycle management out of the box. See [_React component usage_](#core/components/toast.react-component-usage) for an example. | ||
|
||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these docs look good 👍🏽
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
PR Stack
OverlayToaster.createAsync
method to support React 18 #6599Checklist
Changes
Fixes #6239. A new
OverlayToaster.createAsync()
utility is added alongsideOverlayToaster.create()
.This asynchronous function allows a custom
domRenderer
utility to be passed to better support codebases on React 18.The existing
OverlayToaster.create()
method usesReactDOM.render
without any ability to customize this call, which results in a console warning when upgrading to React 18.Why is the new API asynchronous?
The change to Blueprint's API reflects a change in React's API. The new
createRoot
function fromreact-dom/client
no longer renders components synchronously.This is different than
ReactDOM.render
, which does synchronously populate theref
.This seems to be an intentional change in React 18. See the “What about the render callback?” section under Replacing render with createRoot.
Why do I need to pass in a custom
domRenderer
value for React 18?The
createRoot
function is an import onreact-dom/client
. Blueprint is an NPM library that needs to support multiple bundlers (e.g. Webpack, Vite). Most bundlers will fail if it encounters a non-existent submodule import. Since the current major version of Blueprint needs to support React 16 to 18, imports into 18-specific code paths can't be made directly in Blueprint.As a workaround, consumers of Blueprint can provide the
createRoot
function to Blueprint. This is an application of dependency injection.When Blueprint drops support for React 16 in a future major version, the
domRenderer
option will change its current default fromReactDOM.render
to a function using the newcreateRoot
API. This breaking change will makeOverlayToaster.createAsync
easier to use in the future.How do I migrate?
The most one-to-one conversion would be from:
to:
You may notice:
OverlayToaster
component is never unmounted or removed from the DOM after the message is dismissed.Both of these are true of the original synchronous
OverlayToaster.create()
API. The new API makes these existing problems more apparent. We recommend mounting the toaster directly to your React application tree if the first problem is a concern, and sharing the Toaster to avoid the second problem.Reviewers should focus on:
OverlayToaster
tests to make them reusable for bothOverlayToaster.create
andOverlayToaster.createAsync
.@types/react-dom@18
. I can take another stab if we think a React 18 test is valuable to commit, but it's a bit hard to have multiple version of React in the same codebase, even for testing.