-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
864457d
Dynamically generate OverlayToaster.create test
gluxon d59db13
Add OverlayToaster.createAsync method to support React 18
gluxon 220096e
Add OverlayToaster.createAsync test
gluxon 9bdf637
Add OverlayToaster.createAsync interactive example
gluxon 4779ba2
Add link to OverlayToaster examples
gluxon 9b1fb93
Change doc mentions of OverlayToaster.create to createAsync
gluxon d375696
Add memory leak callout when using create/createAsync
gluxon 394a7c2
Add React 18 domRenderer example
gluxon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,16 @@ 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: | ||
```ts | ||
const myToaster: Toaster = await OverlayToaster.createAsync({ position: "bottom" }); | ||
myToaster.show({ ...toastOptions }); | ||
``` | ||
|
||
We recommend calling `OverlayToaster.createAsync` once in your application and [sharing the created instance](#core/components/toast.example) throughout your application. | ||
|
||
A synchronous `OverlayToaster.create()` static method is also available, but will be phased out since React 18+ no longer synchronously renders components to the DOM. | ||
|
||
```ts | ||
const myToaster: Toaster = OverlayToaster.create({ position: "bottom" }); | ||
myToaster.show({ ...toastOptions }); | ||
|
@@ -79,23 +88,49 @@ enable `autoFocus` for an individual `OverlayToaster` via a prop, if desired. | |
|
||
@## Static usage | ||
|
||
__OverlayToaster__ provides the static `create` method that returns a new `Toaster`, rendered into an | ||
__OverlayToaster__ provides the static `createAsync` method that returns a new `Toaster`, rendered into an | ||
element attached to `<body>`. A toaster instance has a collection of methods to show and hide toasts in its given container. | ||
|
||
```ts | ||
OverlayToaster.create(props?: ToasterProps, container = document.body): Toaster | ||
OverlayToaster.createAsync(props?: OverlayToasterProps, options?: OverlayToasterCreateOptions): Promise<Toaster>; | ||
``` | ||
|
||
@interface OverlayToasterCreateOptions | ||
|
||
The toaster will be rendered into a new element appended to the given `container`. | ||
The `container` determines which element toasts are positioned relative to; the default value of `<body>` allows them to use the entire viewport. | ||
|
||
Note that the return type is `Toaster`, which is a minimal interface that exposes only the instance | ||
methods detailed below. It can be thought of as `OverlayToaster` minus the `React.Component` methods, | ||
because the `OverlayToaster` should not be treated as a normal React component. | ||
The return type is `Promise<Toaster>`, which is a minimal interface that exposes only the instance methods detailed | ||
below. It can be thought of as `OverlayToaster` minus the `React.Component` methods, because the `OverlayToaster` should | ||
not be treated as a normal React component. | ||
|
||
A promise is returned as React components cannot be rendered synchronously after React version 18. If this makes | ||
`Toaster` usage difficult outside of a function that's not `async`, it's still possible to attach `.then()` handlers to | ||
the returned toaster. | ||
|
||
Note that `OverlayToaster.create()` will throw an error if invoked inside a component lifecycle method, as | ||
```ts | ||
function synchronousFn() { | ||
const toasterPromise = OverlayToaster.createAsync({}); | ||
toasterPromise.then(toaster => toaster.show({ message: "Toast!" })); | ||
} | ||
``` | ||
|
||
Note that `OverlayToaster.createAsync()` will throw an error if invoked inside a component lifecycle method, as | ||
`ReactDOM.render()` will return `null` resulting in an inaccessible toaster instance. | ||
|
||
<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> | ||
Comment on lines
+121
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
|
||
@interface Toaster | ||
|
||
@### Example | ||
|
@@ -110,7 +145,7 @@ The following code samples demonstrate our preferred pattern for intergrating a | |
import { OverlayToaster, Position } from "@blueprintjs/core"; | ||
|
||
/** Singleton toaster instance. Create separate instances for different options. */ | ||
export const AppToaster = OverlayToaster.create({ | ||
export const AppToaster = OverlayToaster.createAsync({ | ||
className: "recipe-toaster", | ||
position: Position.TOP, | ||
}); | ||
|
@@ -128,14 +163,38 @@ export class App extends React.PureComponent { | |
return <Button onClick={this.showToast} text="Toast please" />; | ||
} | ||
|
||
showToast = () => { | ||
showToast = async () => { | ||
// create toasts in response to interactions. | ||
// in most cases, it's enough to simply create and forget (thanks to timeout). | ||
AppToaster.show({ message: "Toasted." }); | ||
(await AppToaster).show({ message: "Toasted." }); | ||
} | ||
} | ||
``` | ||
|
||
The example below uses the `OverlayToaster.createAsync()` static method. Clicking the button will create a new toaster mounted to `<body>`, show a message, and unmount the toaster from the DOM once the message is dismissed. | ||
|
||
@reactExample ToastCreateAsyncExample | ||
|
||
#### React 18 | ||
|
||
To maintain backwards compatibility with React 16 and 17, `OverlayToaster.createAsync` uses `ReactDOM.render` out of the box. This triggers a [console warning on React 18](https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis). A future major version of Blueprint will drop support for React versions before 18 and switch the default rendering function from `ReactDOM.render` to `createRoot`. | ||
|
||
If you're using React 18, we recommend passing in a custom `domRenderer` function. | ||
|
||
```tsx | ||
import { OverlayToaster } from "@blueprintjs/core"; | ||
import { createRoot } from "react-dom/client"; | ||
|
||
const toaster = await OverlayToaster.createAsync(toasterProps, { | ||
// Use createRoot() instead of ReactDOM.render(). This can be deleted after | ||
// a future Blueprint version uses createRoot() for Toasters by default. | ||
domRenderer: (toaster, containerElement) => createRoot(containerElement).render(toaster), | ||
}); | ||
|
||
toaster.show({ message: "Hello React 18!" }) | ||
``` | ||
|
||
|
||
@## React component usage | ||
|
||
Render the `<OverlayToaster>` component like any other element and supply `<Toast>` elements as `children`. You can | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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()
andOverlayToaster.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 ofcreate
?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 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.Can do! Added in d375696.
Good suggestion. Pushed up a commit that switches the docs and code snippets to
createAsync
here: 9b1fb93