Use react-dom/client createRoot

This commit is contained in:
Fedor Indutny 2025-07-15 16:32:11 -07:00 committed by GitHub
parent 26933bf8d7
commit b30c53d291
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 51 additions and 46 deletions

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { isNumber, groupBy, throttle } from 'lodash'; import { isNumber, groupBy, throttle } from 'lodash';
import { render } from 'react-dom'; import { createRoot } from 'react-dom/client';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import pMap from 'p-map'; import pMap from 'p-map';
import { v7 as generateUuid } from 'uuid'; import { v7 as generateUuid } from 'uuid';
@ -1552,9 +1552,11 @@ export async function startApp(): Promise<void> {
await runAllSyncTasks(); await runAllSyncTasks();
cancelInitializationMessage(); cancelInitializationMessage();
render(
window.Signal.State.Roots.createApp(window.reduxStore), const appContainer = document.getElementById('app-container');
document.getElementById('app-container') strictAssert(appContainer != null, 'No #app-container');
createRoot(appContainer).render(
window.Signal.State.Roots.createApp(window.reduxStore)
); );
const hideMenuBar = window.storage.get('hide-menu-bar', false); const hideMenuBar = window.storage.get('hide-menu-bar', false);
window.IPC.setAutoHideMenuBar(hideMenuBar); window.IPC.setAutoHideMenuBar(hideMenuBar);

View file

@ -1,7 +1,8 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { render, unmountComponentAtNode } from 'react-dom'; import { unmountComponentAtNode } from 'react-dom';
import { createRoot } from 'react-dom/client';
import type { ConversationAttributesType } from '../model-types.d'; import type { ConversationAttributesType } from '../model-types.d';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
@ -389,9 +390,8 @@ export async function joinViaLink(value: string): Promise<void> {
let groupV2InfoNode: HTMLDivElement | undefined = let groupV2InfoNode: HTMLDivElement | undefined =
document.createElement('div'); document.createElement('div');
render( createRoot(groupV2InfoNode).render(
createGroupV2JoinModal(window.reduxStore, { join, onClose: closeDialog }), createGroupV2JoinModal(window.reduxStore, { join, onClose: closeDialog })
groupV2InfoNode
); );
// We declare a new function here so we can await but not block // We declare a new function here so we can await but not block

View file

@ -3,7 +3,7 @@
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import EmbedBlot from '@signalapp/quill-cjs/blots/embed'; import EmbedBlot from '@signalapp/quill-cjs/blots/embed';
import { render } from 'react-dom'; import { createRoot } from 'react-dom/client';
import { Emojify } from '../../components/conversation/Emojify'; import { Emojify } from '../../components/conversation/Emojify';
import { normalizeAci } from '../../util/normalizeAci'; import { normalizeAci } from '../../util/normalizeAci';
@ -46,7 +46,7 @@ export class MentionBlot extends EmbedBlot {
const mentionSpan = document.createElement('span'); const mentionSpan = document.createElement('span');
render( createRoot(mentionSpan).render(
<StrictMode> <StrictMode>
<FunEmojiLocalizationProvider i18n={window.i18n}> <FunEmojiLocalizationProvider i18n={window.i18n}>
<span className="module-composition-input__at-mention"> <span className="module-composition-input__at-mention">
@ -56,8 +56,7 @@ export class MentionBlot extends EmbedBlot {
</bdi> </bdi>
</span> </span>
</FunEmojiLocalizationProvider> </FunEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
mentionSpan
); );
node.appendChild(mentionSpan); node.appendChild(mentionSpan);

View file

@ -2,19 +2,21 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import { render } from 'react-dom'; import { createRoot } from 'react-dom/client';
import { ClearingData } from '../components/ClearingData'; import { ClearingData } from '../components/ClearingData';
import { strictAssert } from '../util/assert';
import { deleteAllData } from './deleteAllData'; import { deleteAllData } from './deleteAllData';
import { FunDefaultEnglishEmojiLocalizationProvider } from '../components/fun/FunEmojiLocalizationProvider'; import { FunDefaultEnglishEmojiLocalizationProvider } from '../components/fun/FunEmojiLocalizationProvider';
export function renderClearingDataView(): void { export function renderClearingDataView(): void {
render( const appContainer = document.getElementById('app-container');
strictAssert(appContainer != null, 'No #app-container');
createRoot(appContainer).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<ClearingData deleteAllData={deleteAllData} i18n={window.i18n} /> <ClearingData deleteAllData={deleteAllData} i18n={window.i18n} />
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
document.getElementById('app-container')
); );
} }

View file

@ -2,7 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import { render, unmountComponentAtNode } from 'react-dom'; import { unmountComponentAtNode } from 'react-dom';
import { createRoot } from 'react-dom/client';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
import { createLogger } from '../logging/log'; import { createLogger } from '../logging/log';
@ -34,13 +35,12 @@ export async function longRunningTaskWrapper<T>({
progressNode = document.createElement('div'); progressNode = document.createElement('div');
log.info(`${idLog}: Creating spinner`); log.info(`${idLog}: Creating spinner`);
render( createRoot(progressNode).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<ProgressModal i18n={window.i18n} /> <ProgressModal i18n={window.i18n} />
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
progressNode
); );
spinnerStart = Date.now(); spinnerStart = Date.now();
}, TWO_SECONDS); }, TWO_SECONDS);

View file

@ -2,7 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import { render, unmountComponentAtNode } from 'react-dom'; import { unmountComponentAtNode } from 'react-dom';
import { createRoot } from 'react-dom/client';
import { ConfirmationDialog } from '../components/ConfirmationDialog'; import { ConfirmationDialog } from '../components/ConfirmationDialog';
import { FunDefaultEnglishEmojiLocalizationProvider } from '../components/fun/FunEmojiLocalizationProvider'; import { FunDefaultEnglishEmojiLocalizationProvider } from '../components/fun/FunEmojiLocalizationProvider';
@ -55,7 +56,7 @@ export function showConfirmationDialog(
confirmationDialogPreviousFocus = document.activeElement as HTMLElement; confirmationDialogPreviousFocus = document.activeElement as HTMLElement;
render( createRoot(confirmationDialogViewNode).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<ConfirmationDialog <ConfirmationDialog
@ -88,7 +89,6 @@ export function showConfirmationDialog(
{options.description} {options.description}
</ConfirmationDialog> </ConfirmationDialog>
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
confirmationDialogViewNode
); );
} }

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import { About } from '../../components/About'; import { About } from '../../components/About';
import { i18n } from '../sandboxedInit'; import { i18n } from '../sandboxedInit';
@ -13,7 +13,10 @@ const { AboutWindowProps } = window.Signal;
strictAssert(AboutWindowProps, 'window values not provided'); strictAssert(AboutWindowProps, 'window values not provided');
ReactDOM.render( const app = document.getElementById('app');
strictAssert(app != null, 'No #app');
createRoot(app).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<About <About
@ -25,6 +28,5 @@ ReactDOM.render(
version={window.SignalContext.getVersion()} version={window.SignalContext.getVersion()}
/> />
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
document.getElementById('app')
); );

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import { render } from 'react-dom'; import { createRoot } from 'react-dom/client';
import { DebugLogWindow } from '../../components/DebugLogWindow'; import { DebugLogWindow } from '../../components/DebugLogWindow';
import { FunDefaultEnglishEmojiLocalizationProvider } from '../../components/fun/FunEmojiLocalizationProvider'; import { FunDefaultEnglishEmojiLocalizationProvider } from '../../components/fun/FunEmojiLocalizationProvider';
import { i18n } from '../sandboxedInit'; import { i18n } from '../sandboxedInit';
@ -12,7 +12,10 @@ const { DebugLogWindowProps } = window.Signal;
strictAssert(DebugLogWindowProps, 'window values not provided'); strictAssert(DebugLogWindowProps, 'window values not provided');
render( const app = document.getElementById('app');
strictAssert(app != null, 'No #app');
createRoot(app).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<DebugLogWindow <DebugLogWindow
@ -23,6 +26,5 @@ render(
uploadLogs={DebugLogWindowProps.uploadLogs} uploadLogs={DebugLogWindowProps.uploadLogs}
/> />
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
document.getElementById('app')
); );

View file

@ -3,8 +3,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { PhoneNumberUtil, PhoneNumberFormat } from 'google-libphonenumber'; import { PhoneNumberUtil, PhoneNumberFormat } from 'google-libphonenumber';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as moment from 'moment'; import * as moment from 'moment';
// @ts-expect-error -- no types // @ts-expect-error -- no types
import 'moment/min/locales.min'; import 'moment/min/locales.min';
@ -49,9 +47,6 @@ window.WebAPI = window.textsecure.WebAPI.initialize({
window.libphonenumberInstance = PhoneNumberUtil.getInstance(); window.libphonenumberInstance = PhoneNumberUtil.getInstance();
window.libphonenumberFormat = PhoneNumberFormat; window.libphonenumberFormat = PhoneNumberFormat;
window.React = React;
window.ReactDOM = ReactDOM;
const { resolvedTranslationsLocale, preferredSystemLocales, localeOverride } = const { resolvedTranslationsLocale, preferredSystemLocales, localeOverride } =
config; config;

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import { PermissionsPopup } from '../../components/PermissionsPopup'; import { PermissionsPopup } from '../../components/PermissionsPopup';
import { i18n } from '../sandboxedInit'; import { i18n } from '../sandboxedInit';
@ -26,7 +26,10 @@ if (forCalling) {
message = i18n('icu:audioPermissionNeeded'); message = i18n('icu:audioPermissionNeeded');
} }
ReactDOM.render( const app = document.getElementById('app');
strictAssert(app != null, 'No #app');
createRoot(app).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<PermissionsPopup <PermissionsPopup
@ -36,6 +39,5 @@ ReactDOM.render(
onClose={PermissionsWindowProps.onClose} onClose={PermissionsWindowProps.onClose}
/> />
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
document.getElementById('app')
); );

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import { CallingScreenSharingController } from '../../components/CallingScreenSharingController'; import { CallingScreenSharingController } from '../../components/CallingScreenSharingController';
import { i18n } from '../sandboxedInit'; import { i18n } from '../sandboxedInit';
@ -28,7 +28,10 @@ function render() {
// Pacify typescript // Pacify typescript
strictAssert(ScreenShareWindowProps, 'window values not provided'); strictAssert(ScreenShareWindowProps, 'window values not provided');
ReactDOM.render( const app = document.getElementById('app');
strictAssert(app != null, 'No #app');
createRoot(app).render(
<StrictMode> <StrictMode>
<FunDefaultEnglishEmojiLocalizationProvider> <FunDefaultEnglishEmojiLocalizationProvider>
<div className="App dark-theme"> <div className="App dark-theme">
@ -41,9 +44,7 @@ function render() {
/> />
</div> </div>
</FunDefaultEnglishEmojiLocalizationProvider> </FunDefaultEnglishEmojiLocalizationProvider>
</StrictMode>, </StrictMode>
document.getElementById('app')
); );
} }
render(); render();