Skip to content

Commit

Permalink
Update React to v18
Browse files Browse the repository at this point in the history
- remove usage of ReactDOM.render and use new root API
- mitigate removal of the React render callback: remove onRender field
from ReactDialog and ReactWidget. Subclasses are now adding the
callbacks themselves.
- list children prop explicitly
- mitigate state batching in SearchInWorkspaceInput
- fix return type in ToolbarImpl.renderGroupsInColumn

Contributed on behalf of STMicroelectronics

Signed-off-by: Alexandra Buzila <abuzila@eclipsesource.com>
  • Loading branch information
AlexandraBuzila committed Jul 22, 2022
1 parent 0e52a3c commit b938e04
Show file tree
Hide file tree
Showing 31 changed files with 176 additions and 134 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [core] renamed `CommonCommands.NEW_FILE` to `CommonCommands.NEW_UNTITLED_FILE` [#11429](https://github.com/eclipse-theia/theia/pull/11429)
- [plugin-ext] `CodeEditorWidgetUtil` moved to `packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts`. `MenusContributionPointHandler` extensively refactored. See PR description for details. [#11290](https://github.com/eclipse-theia/theia/pull/11290)
- [plugin] added support for `DebugProtocolBreakpoint` and `DebugProtocolSource` [#10011](https://github.com/eclipse-theia/theia/issues/10011) - Contributed on behalf of STMicroelectronics
- [core] updated `react` and `react-dom` dependencies to version 18, which introduce new root API for rendering (replaces ReactDOM.render). Since React no longer supports render callbacks, the `onRender` field from `ReactDialog` and `ReactWidget` was removed. - Contributed on behalf of STMicroelectronics

## v1.27.0 - 6/30/2022

Expand Down
3 changes: 2 additions & 1 deletion dependency-check-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"npm/npmjs/-/jschardet/2.3.0": "Approved for Eclipse Theia: https://dev.eclipse.org/ipzilla/show_bug.cgi?id=22481",
"npm/npmjs/-/jsdom/11.12.0": "Approved as 'works-with': https://dev.eclipse.org/ipzilla/show_bug.cgi?id=23640https://dev.eclipse.org/ipzilla/show_bug.cgi?id=23640",
"npm/npmjs/-/lzma-native/8.0.6": "Approved as 'works-with': https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/1850",
"npm/npmjs/-/playwright-core/1.22.2": "Approved as 'works-with': https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/2734"
"npm/npmjs/-/playwright-core/1.22.2": "Approved as 'works-with': https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/2734",
"npm/npmjs/-/request-promise-core/1.1.4": "Approved as 'works-with': https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/3033"
}
10 changes: 10 additions & 0 deletions doc/Migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ For example:
}
```

### v1.28.0

#### React 18 update

The `react` and `react-dom` dependencies were upgraded to version 18. Some relevant changes include:

- `ReactDOM.render` is now deprecated and is replaced by `createRoot` from `react-dom/client`
- the new API no longer supports render callbacks
- updates in promises, setTimeout, event handlers are automatically batched

### v1.24.0

#### node-gyp 8.4.1
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"node": ">=14"
},
"resolutions": {
"**/@types/node": "14"
"**/@types/node": "14",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6"
},
"devDependencies": {
"@types/chai": "4.3.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ export class SomeClass {
- `fs-extra` (from [`fs-extra@^4.0.2`](https://www.npmjs.com/package/fs-extra))
- `fuzzy` (from [`fuzzy@^0.1.3`](https://www.npmjs.com/package/fuzzy))
- `inversify` (from [`inversify@^5.1.1`](https://www.npmjs.com/package/inversify))
- `react-dom` (from [`react-dom@^16.8.0`](https://www.npmjs.com/package/react-dom))
- `react-dom` (from [`react-dom@^18.2.0`](https://www.npmjs.com/package/react-dom))
- `react-dom/client` (from [`react-dom@^18.2.0`](https://www.npmjs.com/package/react-dom))
- `react-virtualized` (from [`react-virtualized@^9.20.0`](https://www.npmjs.com/package/react-virtualized))
- `vscode-languageserver-protocol` (from [`vscode-languageserver-protocol@~3.15.3`](https://www.npmjs.com/package/vscode-languageserver-protocol))
- `vscode-uri` (from [`vscode-uri@^2.1.1`](https://www.npmjs.com/package/vscode-uri))
Expand All @@ -105,7 +106,7 @@ export class SomeClass {
- `lodash.throttle` (from [`lodash.throttle@^4.1.1`](https://www.npmjs.com/package/lodash.throttle))
- `nsfw` (from [`nsfw@^2.1.2`](https://www.npmjs.com/package/nsfw))
- `markdown-it` (from [`markdown-it@^12.3.2`](https://www.npmjs.com/package/markdown-it))
- `react` (from [`react@^16.8.0`](https://www.npmjs.com/package/react))
- `react` (from [`react@^18.2.0`](https://www.npmjs.com/package/react))
- `ws` (from [`ws@^7.1.2`](https://www.npmjs.com/package/ws))
- `yargs` (from [`yargs@^15.3.1`](https://www.npmjs.com/package/yargs))

Expand Down
9 changes: 5 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
"@types/lodash.debounce": "4.0.3",
"@types/lodash.throttle": "^4.1.3",
"@types/markdown-it": "^12.2.3",
"@types/react": "^16.8.0",
"@types/react-dom": "^16.8.0",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-virtualized": "^9.18.3",
"@types/route-parser": "^0.1.1",
"@types/safer-buffer": "^2.1.0",
Expand Down Expand Up @@ -57,8 +57,8 @@
"nsfw": "^2.1.2",
"p-debounce": "^2.1.0",
"perfect-scrollbar": "^1.3.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-tooltip": "^4.2.21",
"react-virtualized": "^9.20.0",
"reflect-metadata": "^0.1.10",
Expand Down Expand Up @@ -109,6 +109,7 @@
"fuzzy",
"inversify",
"react-dom",
"react-dom/client",
"react-virtualized",
"vscode-languageserver-protocol",
"vscode-uri"
Expand Down
1 change: 1 addition & 0 deletions packages/core/shared/react-dom/client/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'react-dom/client';
1 change: 1 addition & 0 deletions packages/core/shared/react-dom/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('react-dom/client');
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import PerfectScrollbar from 'perfect-scrollbar';
import URI from '../../common/uri';
import { Emitter, Event } from '../../common';
import { BreadcrumbPopupContainer } from './breadcrumb-popup-container';
import { DisposableCollection } from '../../common/disposable';
import { CorePreferences } from '../core-preferences';
import { Breadcrumb, Styles } from './breadcrumbs-constants';
import { LabelProvider } from '../label-provider';
Expand Down Expand Up @@ -56,7 +55,6 @@ export class BreadcrumbsRenderer extends ReactRenderer {
protected breadcrumbs: Breadcrumb[] = [];
protected popup: BreadcrumbPopupContainer | undefined;
protected scrollbar: PerfectScrollbar | undefined;
protected toDispose: DisposableCollection = new DisposableCollection();

get active(): boolean {
return !!this.breadcrumbs.length;
Expand Down
13 changes: 6 additions & 7 deletions packages/core/src/browser/dialogs/react-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,27 @@
// *****************************************************************************

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { injectable, inject } from 'inversify';
import { Disposable, DisposableCollection } from '../../common';
import { Disposable } from '../../common';
import { Message } from '../widgets';
import { AbstractDialog, DialogProps } from '../dialogs';
import { createRoot, Root } from 'react-dom/client';

@injectable()
export abstract class ReactDialog<T> extends AbstractDialog<T> {
protected readonly onRender = new DisposableCollection();
protected contentNodeRoot: Root;

constructor(
@inject(DialogProps) props: DialogProps
) {
super(props);
this.toDispose.push(Disposable.create(() => {
ReactDOM.unmountComponentAtNode(this.contentNode);
}));
this.contentNodeRoot = createRoot(this.contentNode);
this.toDispose.push(Disposable.create(() => this.contentNodeRoot.unmount()));
}

protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
ReactDOM.render(<>{this.render()}</>, this.contentNode, () => this.onRender.dispose());
this.contentNodeRoot.render(<>{this.render()}</>);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ export class TabBarToolbar extends ReactWidget {
super();
this.addClass(TabBarToolbar.Styles.TAB_BAR_TOOLBAR);
this.hide();
this.onRender = this.onRender.bind(this);
}

protected onRender = () => this.show();

updateItems(items: Array<TabBarToolbarItem | ReactTabBarToolbarItem>, current: Widget | undefined): void {
this.inline.clear();
this.more.clear();
Expand All @@ -68,11 +71,6 @@ export class TabBarToolbar extends ReactWidget {
if (!items.length) {
this.hide();
}
this.onRender.push(Disposable.create(() => {
if (items.length) {
this.show();
}
}));
this.update();
}

Expand Down Expand Up @@ -129,6 +127,7 @@ export class TabBarToolbar extends ReactWidget {
const toolbarItemClassNames = this.getToolbarItemClassNames(command?.id ?? item.command);
if (item.menuPath && !item.command) { toolbarItemClassNames.push('enabled'); }
return <div key={item.id}
ref={this.onRender}
className={toolbarItemClassNames.join(' ')}
onMouseDown={this.onMouseDownEvent}
onMouseUp={this.onMouseUpEvent}
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/browser/tooltip-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import * as React from 'react';
import ReactTooltip from 'react-tooltip';
import { ReactRenderer, RendererHost } from './widgets/react-renderer';
import { CorePreferences } from './core-preferences';
import { DisposableCollection } from '../common/disposable';
import { v4 } from 'uuid';

export const TooltipService = Symbol('TooltipService');
Expand Down Expand Up @@ -55,7 +54,6 @@ export class TooltipServiceImpl extends ReactRenderer implements TooltipService

public readonly tooltipId: string;
protected rendered = false;
protected toDispose: DisposableCollection = new DisposableCollection();

constructor(
@inject(RendererHost) @optional() host?: RendererHost
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/browser/tree/tree-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { MaybePromise } from '../../common/types';
import { LabelProvider } from '../label-provider';
import { CorePreferences } from '../core-preferences';
import { TreeFocusService } from './tree-focus-service';
import { useEffect } from 'react';

const debounce = require('lodash.debounce');

Expand Down Expand Up @@ -467,6 +468,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
return this.model.root;
}

protected ScrollingRowRenderer: React.FC<{ rows: TreeWidget.NodeRow[] }> = ({ rows }) => {
useEffect(() => this.scrollToSelected());
return <>{rows.map(row => <div key={row.index}>{this.renderNodeRow(row)}</div>)}</>;
};

protected view: TreeWidget.View | undefined;
/**
* Render the tree widget.
Expand All @@ -476,8 +482,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
if (model.root) {
const rows = Array.from(this.rows.values());
if (this.props.virtualized === false) {
this.onRender.push(Disposable.create(() => this.scrollToSelected()));
return rows.map(row => <div key={row.index}>{this.renderNodeRow(row)}</div>);
return <this.ScrollingRowRenderer rows={rows} />;
}
return <TreeWidget.View
ref={view => this.view = (view || undefined)}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/browser/widgets/alert-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const AlertMessageIcon = {
export interface AlertMessageProps {
type: MessageType;
header: string;
children?: React.ReactNode
}

export class AlertMessage extends React.Component<AlertMessageProps> {
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/browser/widgets/react-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,32 @@

import { inject, injectable, optional } from 'inversify';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Disposable } from '../../common';
import { createRoot, Root } from 'react-dom/client';
import { Disposable, DisposableCollection } from '../../common';

export type RendererHost = HTMLElement;
export const RendererHost = Symbol('RendererHost');

@injectable()
export class ReactRenderer implements Disposable {
protected readonly toDispose = new DisposableCollection();
readonly host: HTMLElement;
protected hostRoot: Root;

constructor(
@inject(RendererHost) @optional() host?: RendererHost
) {
this.host = host || document.createElement('div');
this.hostRoot = createRoot(this.host);
this.toDispose.push(Disposable.create(() => this.hostRoot.unmount()));
}

dispose(): void {
ReactDOM.unmountComponentAtNode(this.host);
this.toDispose.dispose();
}

render(): void {
ReactDOM.render(<React.Fragment>{this.doRender()}</React.Fragment>, this.host);
this.hostRoot.render(<React.Fragment>{this.doRender()}</React.Fragment>);
}

protected doRender(): React.ReactNode {
Expand Down
13 changes: 6 additions & 7 deletions packages/core/src/browser/widgets/react-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,31 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { injectable, unmanaged } from 'inversify';
import { DisposableCollection, Disposable } from '../../common';
import { Disposable } from '../../common';
import { BaseWidget, Message } from './widget';
import { Widget } from '@phosphor/widgets';
import { createRoot, Root } from 'react-dom/client';

@injectable()
export abstract class ReactWidget extends BaseWidget {

protected readonly onRender = new DisposableCollection();
protected nodeRoot: Root;

constructor(@unmanaged() options?: Widget.IOptions) {
super(options);
this.scrollOptions = {
suppressScrollX: true,
minScrollbarLength: 35,
};
this.toDispose.push(Disposable.create(() => {
ReactDOM.unmountComponentAtNode(this.node);
}));
this.nodeRoot = createRoot(this.node);
this.toDispose.push(Disposable.create(() => this.nodeRoot.unmount()));
}

protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
ReactDOM.render(<React.Fragment>{this.render()}</React.Fragment>, this.node, () => this.onRender.dispose());
this.nodeRoot.render(<React.Fragment>{this.render()}</React.Fragment>);
}

/**
Expand Down
10 changes: 6 additions & 4 deletions packages/debug/src/browser/editor/debug-breakpoint-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// *****************************************************************************

import * as React from '@theia/core/shared/react';
import * as ReactDOM from '@theia/core/shared/react-dom';
import { createRoot, Root } from '@theia/core/shared/react-dom/client';
import { DebugProtocol } from 'vscode-debugprotocol';
import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
import { Disposable, DisposableCollection, nls } from '@theia/core';
Expand Down Expand Up @@ -55,6 +55,7 @@ export class DebugBreakpointWidget implements Disposable {
protected readonly editorProvider: MonacoEditorProvider;

protected selectNode: HTMLDivElement;
protected selectNodeRoot: Root;

protected zone: MonacoEditorZoneWidget;

Expand Down Expand Up @@ -99,6 +100,8 @@ export class DebugBreakpointWidget implements Disposable {
const selectNode = this.selectNode = document.createElement('div');
selectNode.classList.add('theia-debug-breakpoint-select');
this.zone.containerNode.appendChild(selectNode);
this.selectNodeRoot = createRoot(this.selectNode);
this.toDispose.push(Disposable.create(() => this.selectNodeRoot.unmount()));

const inputNode = document.createElement('div');
inputNode.classList.add('theia-debug-breakpoint-input');
Expand Down Expand Up @@ -148,7 +151,6 @@ export class DebugBreakpointWidget implements Disposable {
this.zone.layout(heightInLines);
this.updatePlaceholder();
}));
this.toDispose.push(Disposable.create(() => ReactDOM.unmountComponentAtNode(selectNode)));
}

dispose(): void {
Expand Down Expand Up @@ -213,14 +215,14 @@ export class DebugBreakpointWidget implements Disposable {
if (this._input) {
this._input.getControl().setValue(this._values[this.context] || '');
}
ReactDOM.render(<SelectComponent
this.selectNodeRoot.render(<SelectComponent
defaultValue={this.context} onChange={this.updateInput}
options={[
{ value: 'condition', label: nls.localizeByDefault('Expression') },
{ value: 'hitCondition', label: nls.localizeByDefault('Hit Count') },
{ value: 'logMessage', label: nls.localizeByDefault('Log Message') },
]}
/>, this.selectNode);
/>);
}

protected readonly updateInput = (option: SelectOption) => {
Expand Down
Loading

0 comments on commit b938e04

Please sign in to comment.