Skip to content

Commit cf6a34c

Browse files
mickrPatrick Murphy
andauthored
Migrate annotations to react18 (#716)
* feat(react18): Update to React 18, replace ReactDOM with ReactDOM.client Migrated codebase from React 17 to React 18. Updated `ReactDOM` imports and usage across multiple files to use `ReactDOM.client`. This included changes to various test files, components, utilities, and the package dependencies. * feat(react18): refactor imports and remove unnecessary dependencies Simplified import syntax across multiple components, moving from wildcard imports to direct imports. Removed unnecessary `usePrevious` hooks to streamline popup update logic and updated `yarn.lock` to newer versions of several dependencies. * feat(react18): refactor imports and remove unnecessary dependencies Refactored components to improve readability and consistency. Updated package dependencies and ESLint configurations for better code quality and compatibility with the latest libraries. Adjustments include moving to "react-jsx," improving TypeScript definitions, and simplifying utility functions. * feat(react18): Mock react-redux in RegionAnnotation tests Mocking the entire react-redux module to control useSelector behavior. This ensures test consistency by avoiding real state dependencies. * feat(react18): Refactor PopupReply imports Replaced `ReactRedux.useSelector` with `useSelector` in PopupReply for consistency. Also, removed unused and commented-out code in global type definitions to improve code clarity. * feat(react18): Disable ESLint for undefined variables in popperMock Added a directive to disable ESLint checks for undefined variables in the popperMock.js script. This change ensures that the createPopper mock function can be defined without ESLint errors during Jest testing. * feat(react18): Add back tests, remove unused files * In BaseManager, added back deleting the root react node on destroy * Removed usePrevious files - no longer used * Added mocks of a mangager to fix ImageAnnotator unit tests of getReference and init functions * Minor style changes * feat(react18): add optional chaining to ensure not null * feat(react18): Remove whitespace * feat(react18): Add useMemo to avoid re-renders and fix eslint issue --------- Co-authored-by: Patrick Murphy <pmurphy@box.com>
1 parent a19740e commit cf6a34c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3170
-1767
lines changed

.eslintrc.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
const eslintrc = require.resolve('@box/frontend/eslint/eslintrc.js');
2-
31
module.exports = {
4-
extends: [eslintrc],
2+
extends: [
3+
require.resolve('@box/frontend/eslint/base'),
4+
require.resolve('@box/frontend/eslint/react'),
5+
require.resolve('@box/frontend/eslint/typescript'),
6+
],
57
rules: {
68
'class-methods-use-this': 0, // fixme
79
'flowtype/no-types-missing-file-annotation': 'off', // Allows types in TS files
@@ -22,8 +24,9 @@ module.exports = {
2224
{
2325
files: ['**/__mocks__/*', '**/__tests__/*'],
2426
rules: {
25-
'@typescript-eslint/camelcase': 'off',
27+
'@typescript-eslint/naming-convention': 'off',
2628
'@typescript-eslint/no-non-null-assertion': 'off',
29+
'import/no-import-module-exports': 'off'
2730
},
2831
},
2932
{

package.json

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@
1616
"yarn": ">=1.10.x"
1717
},
1818
"devDependencies": {
19-
"@babel/cli": "^7.8.4",
20-
"@babel/core": "^7.9.0",
21-
"@babel/plugin-proposal-class-properties": "^7.8.3",
22-
"@babel/plugin-transform-flow-strip-types": "^7.9.0",
23-
"@babel/plugin-transform-object-assign": "^7.8.3",
24-
"@babel/plugin-transform-runtime": "^7.9.0",
25-
"@babel/polyfill": "^7.8.7",
26-
"@babel/preset-env": "^7.9.5",
27-
"@babel/preset-react": "^7.9.4",
28-
"@babel/preset-typescript": "^7.9.0",
29-
"@box/frontend": "^6.4.0",
19+
"@babel/cli": "^7.25.9",
20+
"@babel/core": "^7.25.9",
21+
"@babel/eslint-parser": "^7.24.7",
22+
"@babel/plugin-proposal-class-properties": "^7.18.6",
23+
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
24+
"@babel/plugin-transform-flow-strip-types": "^7.25.9",
25+
"@babel/plugin-transform-object-assign": "^7.25.9",
26+
"@babel/plugin-transform-runtime": "^7.25.9",
27+
"@babel/preset-env": "^7.25.9",
28+
"@babel/preset-react": "^7.25.9",
29+
"@babel/preset-typescript": "^7.25.9",
30+
"@box/frontend": "^10.0.0",
3031
"@box/languages": "^1.1.0",
32+
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
3133
"@commitlint/cli": "^8.3.5",
3234
"@commitlint/config-conventional": "^8.2.0",
3335
"@formatjs/intl-pluralrules": "^1.5.5",
@@ -39,13 +41,12 @@
3941
"@types/enzyme": "^3.10.5",
4042
"@types/jest": "^24.9.0",
4143
"@types/lodash": "^4.14.150",
42-
"@types/react": "^17.0.2",
43-
"@types/react-dom": "^17.0.1",
44-
"@types/react-redux": "^7.1.16",
44+
"@types/react": "18.3.12",
45+
"@types/react-dom": "18.3.1",
46+
"@types/react-redux": "^7.1.33",
4547
"@types/uuid": "^8.3.0",
46-
"@typescript-eslint/eslint-plugin": "^2.29.0",
47-
"@typescript-eslint/parser": "^2.29.0",
48-
"@wojtekmaj/enzyme-adapter-react-17": "^0.4.1",
48+
"@typescript-eslint/eslint-plugin": "^7.3.1",
49+
"@typescript-eslint/parser": "^7.3.1",
4950
"autoprefixer": "^9.7.6",
5051
"axios": "^0.24.0",
5152
"babel-eslint": "^10.1.0",
@@ -59,26 +60,26 @@
5960
"circular-dependency-plugin": "^5.2.0",
6061
"classnames": "^2.2.5",
6162
"conventional-github-releaser": "^3.1.3",
62-
"core-js": "^3.6.5",
63+
"core-js": "^3.38.1",
6364
"css-loader": "^3.5.2",
6465
"cypress": "^4.4.1",
6566
"draft-js": "0.10.5",
6667
"enzyme": "^3.11.0",
6768
"enzyme-to-json": "^3.4.4",
68-
"eslint": "^6.7.2",
69-
"eslint-config-airbnb": "^18.1.0",
70-
"eslint-config-prettier": "^6.11.0",
69+
"eslint": "^8.57.0",
70+
"eslint-config-airbnb": "^19.0.4",
71+
"eslint-config-prettier": "^9.1.0",
7172
"eslint-plugin-babel": "^5.3.0",
7273
"eslint-plugin-cypress": "^2.10.3",
73-
"eslint-plugin-flowtype": "^4.7.0",
74-
"eslint-plugin-formatjs": "^1.5.4",
75-
"eslint-plugin-import": "^2.20.2",
74+
"eslint-plugin-flowtype": "^8.0.3",
75+
"eslint-plugin-formatjs": "^4.13.3",
76+
"eslint-plugin-import": "^2.26.0",
7677
"eslint-plugin-jest": "^23.8.2",
7778
"eslint-plugin-jsx-a11y": "^6.2.3",
78-
"eslint-plugin-lodash": "^6.0.0",
79-
"eslint-plugin-prettier": "^3.1.3",
80-
"eslint-plugin-react": "^7.19.0",
81-
"eslint-plugin-react-hooks": "^1.7.0",
79+
"eslint-plugin-lodash": "^7.4.0",
80+
"eslint-plugin-prettier": "^5.2.1",
81+
"eslint-plugin-react": "^7.34.3",
82+
"eslint-plugin-react-hooks": "^4.6.0",
8283
"formik": "^2.1.4",
8384
"husky": "^3.1.0",
8485
"jest": "^24.9.0",
@@ -92,13 +93,14 @@
9293
"postcss-loader": "^3.0.0",
9394
"prettier": "^1.19.1",
9495
"raw-loader": "^4.0.1",
95-
"react": "^17.0.1",
96-
"react-dom": "^17.0.1",
96+
"react": "18.3.1",
97+
"react-dom": "18.3.1",
9798
"react-intl": "6.4.2",
98-
"react-redux": "^7.2.2",
99+
"react-redux": "^9.1.2",
99100
"react-tether": "1.0.5",
100101
"react-textarea-autosize": "^7.1.2",
101102
"redux": "^4.0.5",
103+
"regenerator-runtime": "^0.13.9",
102104
"sass": "^1.64.1",
103105
"sass-loader": "^8.0.2",
104106
"scroll-into-view-if-needed": "^2.2.24",
@@ -109,7 +111,7 @@
109111
"stylelint-order": "^3.1.1",
110112
"tabbable": "^1.1.3",
111113
"terser-webpack-plugin": "^4.2.3",
112-
"typescript": "^3.8.3",
114+
"typescript": "4.9.5",
113115
"uuid": "^8.3.1",
114116
"wait-on": "^4.0.2",
115117
"webpack": "^4.47.0",
@@ -153,6 +155,8 @@
153155
}
154156
},
155157
"resolutions": {
156-
"**/react-intl/**/@types/react": "^17.0.2"
158+
"**/react-intl/**/@types/react": "^18.3.12",
159+
"**/@types/react": "^18.3.12",
160+
"**/@types/react-dom": "^18.2.0"
157161
}
158162
}

scripts/jest/enzyme-adapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'core-js/es/map';
22
import 'core-js/es/set';
33
import Enzyme, { mount, shallow } from 'enzyme';
4-
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
4+
import Adapter from '@cfaester/enzyme-adapter-react-18';
55

66
Enzyme.configure({ adapter: new Adapter() });
77

scripts/jest/popperMock.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-undef */
12
const createPopper = jest.fn(() => ({
23
destroy: jest.fn(),
34
forceUpdate: jest.fn(),

src/@types/global.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-namespace */
2-
declare namespace Intl {
3-
const RelativeTimeFormat: {};
4-
}
5-
2+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
63
declare namespace NodeJS {
74
interface Global {
85
window: any;

src/common/BaseManager.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as ReactDOM from 'react-dom';
1+
import * as ReactDOM from 'react-dom/client';
22
import { IntlShape } from 'react-intl';
33
import { Store } from 'redux';
44
import { applyResinTags } from '../utils/resin';
@@ -28,13 +28,17 @@ export default class BaseManager implements Manager {
2828

2929
reactEl: HTMLElement;
3030

31+
root: ReactDOM.Root | null = null;
32+
3133
constructor({ location = 1, referenceEl, resinTags = {} }: Options) {
3234
this.location = location;
3335
this.reactEl = this.insert(referenceEl, {
3436
...resinTags,
3537
feature: 'annotations',
3638
});
3739

40+
this.root = ReactDOM.createRoot(this.reactEl);
41+
3842
this.decorate();
3943
}
4044

@@ -43,11 +47,11 @@ export default class BaseManager implements Manager {
4347
}
4448

4549
destroy(): void {
46-
ReactDOM.unmountComponentAtNode(this.reactEl);
47-
48-
const parentEl = this.reactEl.parentNode;
49-
if (parentEl) {
50-
parentEl.removeChild(this.reactEl);
50+
if (this.root) {
51+
this.root.unmount();
52+
}
53+
if (this.reactEl.parentNode) {
54+
this.reactEl.parentNode.removeChild(this.reactEl);
5155
}
5256
}
5357

src/common/DeselectManager.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import * as ReactDOM from 'react-dom';
2+
import * as ReactDOM from 'react-dom/client';
33
import { Provider } from 'react-redux';
44
import { Store } from 'redux';
55
import BaseManager from './BaseManager';
@@ -20,11 +20,14 @@ export default class DeselectManager extends BaseManager {
2020
}
2121

2222
render({ store }: Props): void {
23-
ReactDOM.render(
23+
if (!this.root) {
24+
this.root = ReactDOM.createRoot(this.reactEl);
25+
}
26+
27+
this.root.render(
2428
<Provider store={store}>
2529
<DeselectListener />
2630
</Provider>,
27-
this.reactEl,
2831
);
2932
}
3033
}

src/common/__tests__/BaseManager-test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import * as ReactDOM from 'react-dom';
21
import BaseManager, { Options, Props } from '../BaseManager';
32

4-
jest.mock('react-dom');
3+
jest.mock('react-dom/client', () => ({
4+
createRoot: jest.fn().mockReturnValue({
5+
render: jest.fn(),
6+
unmount: jest.fn(),
7+
}),
8+
}));
9+
510
class TestBaseManager extends BaseManager {
611
// eslint-disable-next-line @typescript-eslint/no-empty-function
712
decorate(): void {}
@@ -47,7 +52,7 @@ describe('BaseManager', () => {
4752
const wrapper = getWrapper();
4853
wrapper.destroy();
4954

50-
expect(ReactDOM.unmountComponentAtNode).toHaveBeenCalledWith(wrapper.reactEl);
55+
expect(wrapper.root?.unmount).toHaveBeenCalled();
5156
expect(removeChildSpy).toHaveBeenCalledWith(wrapper.reactEl);
5257
});
5358
});

src/common/__tests__/DeselectManager-test.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import ReactDOM from 'react-dom';
1+
import ReactDOM from 'react-dom/client';
22
import DeselectManager from '../DeselectManager';
33
import { createStore } from '../../store';
44
import { Options } from '../BaseManager';
55

6-
jest.mock('react-dom', () => ({
7-
render: jest.fn(),
8-
unmountComponentAtNode: jest.fn(),
6+
jest.mock('react-dom/client', () => ({
7+
createRoot: jest.fn().mockReturnValue({
8+
render: jest.fn(),
9+
unmount: jest.fn(),
10+
}),
911
}));
1012

1113
describe('DeselectManager', () => {
@@ -34,10 +36,10 @@ describe('DeselectManager', () => {
3436
describe('render()', () => {
3537
test('should format the props and pass them to the underlying components', () => {
3638
const wrapper = getWrapper();
37-
39+
const root = ReactDOM.createRoot(rootEl);
3840
wrapper.render({ store: createStore() });
3941

40-
expect(ReactDOM.render).toHaveBeenCalled();
42+
expect(root.render).toHaveBeenCalled();
4143
});
4244
});
4345
});

src/common/__tests__/usePrevious-test.tsx

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)