Skip to content

Commit

Permalink
Migrate to React 18 createRoot API
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Telatynski <[email protected]>
  • Loading branch information
t3chguy committed Oct 21, 2024
1 parent 8ff55a9 commit 0bf492e
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 138 deletions.
4 changes: 2 additions & 2 deletions src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ Please see LICENSE files in the repository root for full details.
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
import "@types/modernizr";

import type { Renderer } from "react-dom";
import type { logger } from "matrix-js-sdk/src/logger";
import ContentMessages from "../ContentMessages";
import { IMatrixClientPeg } from "../MatrixClientPeg";
Expand Down Expand Up @@ -44,6 +43,7 @@ import AutoRageshakeStore from "../stores/AutoRageshakeStore";
import { IConfigOptions } from "../IConfigOptions";
import { MatrixDispatcher } from "../dispatcher/dispatcher";
import { DeepReadonly } from "./common";
import MatrixChat from "../components/structures/MatrixChat";

/* eslint-disable @typescript-eslint/naming-convention */

Expand Down Expand Up @@ -71,7 +71,7 @@ declare global {
interface Window {
mxSendRageshake: (text: string, withLogs?: boolean) => void;
matrixLogger: typeof logger;
matrixChat: ReturnType<Renderer>;
matrixChat: MatrixChat;
mxSendSentryReport: (userText: string, issueUrl: string, error: Error) => Promise<void>;
mxLoginWithAccessToken: (hsUrl: string, accessToken: string) => Promise<void>;
mxAutoRageshakeStore?: AutoRageshakeStore;
Expand Down
46 changes: 25 additions & 21 deletions src/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import ReactDOM from "react-dom";
import { createRoot, Root } from "react-dom/client";
import classNames from "classnames";
import { IDeferred, defer, sleep } from "matrix-js-sdk/src/utils";
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
Expand Down Expand Up @@ -83,28 +83,26 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
// Neither the static nor priority modal will be in this list.
private modals: IModal<any>[] = [];

private static getOrCreateContainer(): HTMLElement {
let container = document.getElementById(DIALOG_CONTAINER_ID);

if (!container) {
container = document.createElement("div");
private static root?: Root;
private static getOrCreateRoot(): Root {
if (!ModalManager.root) {
const container = document.createElement("div");
container.id = DIALOG_CONTAINER_ID;
document.body.appendChild(container);
ModalManager.root = createRoot(container);
}

return container;
return ModalManager.root;
}

private static getOrCreateStaticContainer(): HTMLElement {
let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID);

if (!container) {
container = document.createElement("div");
private static staticRoot?: Root;
private static getOrCreateStaticRoot(): Root {
if (!ModalManager.staticRoot) {
const container = document.createElement("div");
container.id = STATIC_DIALOG_CONTAINER_ID;
document.body.appendChild(container);
ModalManager.staticRoot = createRoot(container);
}

return container;
return ModalManager.staticRoot;
}

public constructor() {
Expand Down Expand Up @@ -400,8 +398,10 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
dis.dispatch({
action: "aria_unhide_main_app",
});
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
ModalManager.getOrCreateRoot().unmount();
ModalManager.root = undefined;
ModalManager.getOrCreateStaticRoot().unmount();
ModalManager.staticRoot = undefined;
return;
}

Expand Down Expand Up @@ -430,10 +430,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
</TooltipProvider>
);

ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
ModalManager.getOrCreateStaticRoot().render(staticDialog);
} else {
// This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
ModalManager.getOrCreateStaticRoot().unmount();
ModalManager.staticRoot = undefined;
}

const modal = this.getCurrentModal();
Expand All @@ -457,10 +458,13 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
</TooltipProvider>
);

setTimeout(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()), 0);
setTimeout(() => {
ModalManager.getOrCreateRoot().render(dialog);
}, 0);
} else {
// This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
ModalManager.getOrCreateRoot().unmount();
ModalManager.root = undefined;
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/components/views/elements/PersistedElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React, { MutableRefObject, ReactNode } from "react";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { TooltipProvider } from "@vector-im/compound-web";

Expand Down Expand Up @@ -176,7 +176,8 @@ export default class PersistedElement extends React.Component<IProps> {
</MatrixClientContext.Provider>
);

ReactDOM.render(content, getOrCreateContainer("mx_persistedElement_" + this.props.persistKey));
const root = createRoot(getOrCreateContainer("mx_persistedElement_" + this.props.persistKey));
root.render(content);
}

private updateChildVisibility(child?: HTMLDivElement, visible = false): void {
Expand Down
15 changes: 8 additions & 7 deletions src/components/views/messages/EditHistoryMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import classNames from "classnames";
import * as HtmlUtils from "../../../HtmlUtils";
import { editBodyDiffToHtml } from "../../../utils/MessageDiffUtils";
import { formatTime } from "../../../DateUtils";
import { pillifyLinks, unmountPills } from "../../../utils/pillify";
import { tooltipifyLinks, unmountTooltips } from "../../../utils/tooltipify";
import { pillifyLinks } from "../../../utils/pillify";
import { tooltipifyLinks } from "../../../utils/tooltipify";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import RedactedBody from "./RedactedBody";
Expand All @@ -23,6 +23,7 @@ import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
import ViewSource from "../../structures/ViewSource";
import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ReactRootManager } from "../../../utils/react";

function getReplacedContent(event: MatrixEvent): IContent {
const originalContent = event.getOriginalContent();
Expand All @@ -47,8 +48,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
public declare context: React.ContextType<typeof MatrixClientContext>;

private content = createRef<HTMLDivElement>();
private pills: Element[] = [];
private tooltips: Element[] = [];
private pills = new ReactRootManager();
private tooltips = new ReactRootManager();

public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context);
Expand Down Expand Up @@ -103,7 +104,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
private tooltipifyLinks(): void {
// not present for redacted events
if (this.content.current) {
tooltipifyLinks(this.content.current.children, this.pills, this.tooltips);
tooltipifyLinks(this.content.current.children, this.pills.elements, this.tooltips);
}
}

Expand All @@ -113,8 +114,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
}

public componentWillUnmount(): void {
unmountPills(this.pills);
unmountTooltips(this.tooltips);
this.pills.unmount();
this.tooltips.unmount();
const event = this.props.mxEvent;
event.localRedactionEvent()?.off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
}
Expand Down
37 changes: 18 additions & 19 deletions src/components/views/messages/TextualBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
*/

import React, { createRef, SyntheticEvent, MouseEvent } from "react";
import ReactDOM from "react-dom";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";

Expand All @@ -17,8 +16,8 @@ import Modal from "../../../Modal";
import dis from "../../../dispatcher/dispatcher";
import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import { pillifyLinks, unmountPills } from "../../../utils/pillify";
import { tooltipifyLinks, unmountTooltips } from "../../../utils/tooltipify";
import { pillifyLinks } from "../../../utils/pillify";
import { tooltipifyLinks } from "../../../utils/tooltipify";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks";
import { Action } from "../../../dispatcher/actions";
Expand All @@ -36,6 +35,7 @@ import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
import { IEventTileOps } from "../rooms/EventTile";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import CodeBlock from "./CodeBlock";
import { ReactRootManager } from "../../../utils/react";

interface IState {
// the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody.
Expand All @@ -48,9 +48,9 @@ interface IState {
export default class TextualBody extends React.Component<IBodyProps, IState> {
private readonly contentRef = createRef<HTMLDivElement>();

private pills: Element[] = [];
private tooltips: Element[] = [];
private reactRoots: Element[] = [];
private pills = new ReactRootManager();
private tooltips = new ReactRootManager();
private reactRoots = new ReactRootManager();

public static contextType = RoomContext;
public declare context: React.ContextType<typeof RoomContext>;
Expand Down Expand Up @@ -80,7 +80,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip
// container is empty before the internal component has mounted so calculateUrlPreview
// won't find any anchors
tooltipifyLinks([content], this.pills, this.tooltips);
tooltipifyLinks([content], [...this.pills.elements, ...this.reactRoots.elements], this.tooltips);

if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
// Handle expansion and add buttons
Expand Down Expand Up @@ -116,7 +116,12 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// Insert containing div in place of <pre> block
pre.parentNode?.replaceChild(root, pre);

ReactDOM.render(<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>, root);
this.reactRoots.render(
<StrictMode>

Check failure on line 120 in src/components/views/messages/TextualBody.tsx

View workflow job for this annotation

GitHub Actions / ESLint

'StrictMode' is not defined

Check failure on line 120 in src/components/views/messages/TextualBody.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Cannot find name 'StrictMode'.
<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>
</StrictMode>,

Check failure on line 122 in src/components/views/messages/TextualBody.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Cannot find name 'StrictMode'.
root,
);
}

public componentDidUpdate(prevProps: Readonly<IBodyProps>): void {
Expand All @@ -130,16 +135,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
}

public componentWillUnmount(): void {
unmountPills(this.pills);
unmountTooltips(this.tooltips);

for (const root of this.reactRoots) {
ReactDOM.unmountComponentAtNode(root);
}

this.pills = [];
this.tooltips = [];
this.reactRoots = [];
this.pills.unmount();
this.tooltips.unmount();
this.reactRoots.unmount();
}

public shouldComponentUpdate(nextProps: Readonly<IBodyProps>, nextState: Readonly<IState>): boolean {
Expand Down Expand Up @@ -195,7 +193,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
</TooltipProvider>
);

ReactDOM.render(spoiler, spoilerContainer);
this.reactRoots.render(spoiler, spoilerContainer);

node.parentNode?.replaceChild(spoilerContainer, node);

node = spoilerContainer;
Expand Down
10 changes: 6 additions & 4 deletions src/utils/exportUtils/HtmlExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { Room, MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
import { renderToStaticMarkup } from "react-dom/server";
import { logger } from "matrix-js-sdk/src/logger";
Expand Down Expand Up @@ -313,9 +313,11 @@ export default class HTMLExporter extends Exporter {
) {
// to linkify textual events, we'll need lifecycle methods which won't be invoked in renderToString
// So, we'll have to render the component into a temporary root element
const tempRoot = document.createElement("div");
ReactDOM.render(EventTile, tempRoot);
eventTileMarkup = tempRoot.innerHTML;
const tempElement = document.createElement("div");
const tempRoot = createRoot(tempElement);
tempRoot.render(EventTile);
eventTileMarkup = tempElement.innerHTML;
tempRoot.unmount();
} else {
eventTileMarkup = renderToStaticMarkup(EventTile);
}
Expand Down
4 changes: 1 addition & 3 deletions src/utils/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import type MatrixChat from "../components/structures/MatrixChat";
import Views from "../Views";

export function isLoggedIn(): boolean {
// JRS: Maybe we should move the step that writes this to the window out of
// `element-web` and into this file? Better yet, we should probably create a
// store to hold this state.
// See also https://github.com/vector-im/element-web/issues/15034.
const app = window.matrixChat;
return (app as MatrixChat)?.state.view === Views.LOGGED_IN;
return window.matrixChat?.state.view === Views.LOGGED_IN;
}
Loading

0 comments on commit 0bf492e

Please sign in to comment.