From 26f8f0f10fa7e3eba1195bc8ff8c7d586d0953d6 Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Sat, 21 Jan 2023 17:43:20 +0300 Subject: [PATCH] fix(Toaster): pre 18 react fix (#494) --- src/components/Toaster/README.md | 16 +++- src/components/Toaster/ToasterSingleton.tsx | 25 ++++- .../Toaster/ToasterSingletonReact18.tsx | 92 ------------------- src/components/Toaster/index.ts | 1 - src/toaster-singleton-react-18.ts | 5 +- 5 files changed, 36 insertions(+), 103 deletions(-) delete mode 100644 src/components/Toaster/ToasterSingletonReact18.tsx diff --git a/src/components/Toaster/README.md b/src/components/Toaster/README.md index 1bf0d1de6f..a68edb7a28 100644 --- a/src/components/Toaster/README.md +++ b/src/components/Toaster/README.md @@ -59,6 +59,9 @@ const FoobarWithToaster = withToaster()(FoobarComponent); ## Usage as singleton +Toaster has singleton, so when initialized in different parts of the application, the same instance will be returned. +On initialization it is possible to pass className, that will be assigned to dom-element which is wrapping all toasts. + ### React < 18 ```js @@ -66,6 +69,8 @@ import {Toaster} from '@gravity-ui/uikit'; const toaster = new Toaster(); ``` +or + ```js import {toaster} from '@gravity-ui/uikit/toaster-singleton'; ``` @@ -73,17 +78,18 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton'; ### React 18 ```js -import {ToasterReact18} from '@gravity-ui/uikit'; -const toaster = new ToasterReact18(); +import ReactDOMClient from 'react-dom/client'; +import {Toaster} from '@gravity-ui/uikit'; +Toaster.injectReactDOMClient(ReactDOMClient); +const toaster = new Toaster(); ``` +or + ```js import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18'; ``` -Toaster has singleton, so when initialized in different parts of the application, the same instance will be returned. -On initialization it is possible to pass className, that will be assigned to dom-element which is wrapping all toasts. - ## Constructor arguments | Parameter | Type | Default | Description | diff --git a/src/components/Toaster/ToasterSingleton.tsx b/src/components/Toaster/ToasterSingleton.tsx index 634b7b5268..0ae7e08d3d 100644 --- a/src/components/Toaster/ToasterSingleton.tsx +++ b/src/components/Toaster/ToasterSingleton.tsx @@ -8,6 +8,7 @@ import {ToasterComponent} from './ToasterComponent/ToasterComponent'; const TOASTER_KEY: unique symbol = Symbol('Toaster instance key'); const bToaster = block('toaster'); +let ReactDOMClient: any; declare global { interface Window { @@ -16,7 +17,12 @@ declare global { } export class ToasterSingleton { + static injectReactDOMClient(client: any) { + ReactDOMClient = client; + } + private rootNode!: HTMLDivElement; + private reactRoot!: any; private className = ''; private mobile = false; private componentAPI: null | ToasterPublicMethods = null; @@ -36,6 +42,7 @@ export class ToasterSingleton { this.className = className; this.mobile = mobile; this.createRootNode(); + this.createReactRoot(); this.render(); window[TOASTER_KEY] = this; @@ -68,18 +75,28 @@ export class ToasterSingleton { document.body.appendChild(this.rootNode); } + private createReactRoot() { + if (ReactDOMClient) { + this.reactRoot = ReactDOMClient.createRoot(this.rootNode); + } + } + private render() { - ReactDOM.render( + const container = ( { this.componentAPI = api; }} > - , - this.rootNode, - () => Promise.resolve(), + ); + + if (this.reactRoot) { + this.reactRoot.render(container); + } else { + ReactDOM.render(container, this.rootNode, () => Promise.resolve()); + } } private setRootNodeClassName() { diff --git a/src/components/Toaster/ToasterSingletonReact18.tsx b/src/components/Toaster/ToasterSingletonReact18.tsx deleted file mode 100644 index fd3a63dcf3..0000000000 --- a/src/components/Toaster/ToasterSingletonReact18.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import ReactDOMClient from 'react-dom/client'; -import get from 'lodash/get'; -import {block} from '../utils/cn'; -import type {ToasterArgs, ToasterPublicMethods, ToastProps} from './types'; -import {ToasterProvider} from './Provider/ToasterProvider'; -import {ToasterComponent} from './ToasterComponent/ToasterComponent'; - -const TOASTER_KEY: unique symbol = Symbol('Toaster instance key'); -const bToaster = block('toaster'); - -declare global { - interface Window { - [TOASTER_KEY]: ToasterSingleton; - } -} - -export class ToasterSingleton { - private rootNode!: HTMLDivElement; - private reactRoot!: ReactDOMClient.Root; - private className = ''; - private mobile = false; - private componentAPI: null | ToasterPublicMethods = null; - - constructor(args?: ToasterArgs) { - const className = get(args, ['className'], ''); - const mobile = get(args, ['mobile'], false); - - if (window[TOASTER_KEY] instanceof ToasterSingleton) { - const me = window[TOASTER_KEY]; - me.className = className; - me.mobile = mobile; - me.setRootNodeClassName(); - return me; - } - - this.className = className; - this.mobile = mobile; - this.createRootNode(); - this.createReactRoot(); - this.render(); - - window[TOASTER_KEY] = this; - } - - destroy() { - this.reactRoot.unmount(); - this.rootNode.remove(); - } - - add = (options: ToastProps) => { - this.componentAPI?.add(options); - }; - - remove = (name: string) => { - this.componentAPI?.remove(name); - }; - - removeAll = () => { - this.componentAPI?.removeAll(); - }; - - update = (name: string, overrideOptions: Partial) => { - this.componentAPI?.update(name, overrideOptions); - }; - - private createRootNode() { - this.rootNode = document.createElement('div'); - this.setRootNodeClassName(); - document.body.appendChild(this.rootNode); - } - - private createReactRoot() { - this.reactRoot = ReactDOMClient.createRoot(this.rootNode); - } - - private render() { - this.reactRoot.render( - { - this.componentAPI = api; - }} - > - - , - ); - } - - private setRootNodeClassName() { - this.rootNode.className = bToaster({mobile: this.mobile}, this.className); - } -} diff --git a/src/components/Toaster/index.ts b/src/components/Toaster/index.ts index e82778d332..58dffca04d 100644 --- a/src/components/Toaster/index.ts +++ b/src/components/Toaster/index.ts @@ -1,5 +1,4 @@ export {ToasterSingleton as Toaster} from './ToasterSingleton'; -export {ToasterSingleton as ToasterReact18} from './ToasterSingletonReact18'; export {Toast} from './Toast/Toast'; export * from './types'; diff --git a/src/toaster-singleton-react-18.ts b/src/toaster-singleton-react-18.ts index 1b6a70b2cb..3b2e8497a6 100644 --- a/src/toaster-singleton-react-18.ts +++ b/src/toaster-singleton-react-18.ts @@ -1,4 +1,7 @@ -import {ToasterSingleton} from './components/Toaster/ToasterSingletonReact18'; +import ReactDOMClient from 'react-dom/client'; +import {ToasterSingleton} from './components/Toaster/ToasterSingleton'; + +ToasterSingleton.injectReactDOMClient(ReactDOMClient); // in SSR case export const toaster = typeof window === 'object' ? new ToasterSingleton() : null;