diff --git a/package.json b/package.json index ca946437c61..3c9080a1cdc 100644 --- a/package.json +++ b/package.json @@ -79,15 +79,15 @@ "@swc/core": "^1.3.107", "@swc/jest": "^0.2.36", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "12.1.2", + "@testing-library/react": "^15.0.5", "@types/chrome": "0.0.260", "@types/jest": "^29.5.11", "@types/jsdom": "^21.1.6", "@types/lodash": "^4.14.202", "@types/luxon": "^3.4.2", "@types/node": "^16.11.7", - "@types/react": "^16.14.25", - "@types/react-dom": "^16.9.15", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", "@types/react-helmet": "^6.1.11", "@types/react-router-dom": "^5.3.3", "@types/serve-static": "^1.15.5", @@ -151,7 +151,7 @@ "webpack-node-externals": "^3.0.0" }, "dependencies": { - "@fluentui/react": "^8.96.1", + "@fluentui/react": "^8.118.1", "@microsoft/applicationinsights-web": "^2.8.15", "@testing-library/user-event": "^14.5.2", "ajv": "^8.12.0", @@ -160,9 +160,9 @@ "idb-keyval": "^6.2.1", "lodash": "^4.17.21", "luxon": "^3.4.4", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-helmet": "^6.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-helmet-async": "^2.0.5", "react-resize-detector": "^9.1.1", "react-router-dom": "^6.21.3", "tabbable": "^6.2.0", diff --git a/packages/report/package.json b/packages/report/package.json index 6bfc40d9fb7..56b8497ad2e 100644 --- a/packages/report/package.json +++ b/packages/report/package.json @@ -18,14 +18,14 @@ "url": "https://github.com/Microsoft/accessibility-insights-web" }, "dependencies": { - "@fluentui/react": "^8.96.1", + "@fluentui/react": "^8.118.1", "axe-core": "4.8.4", "classnames": "^2.5.1", "lodash": "^4.17.21", "luxon": "^3.4.4", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-helmet": "^6.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-helmet-async": "^2.0.5", "uuid": "^9.0.1" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index a972de8848a..c44dc002fa1 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -22,9 +22,9 @@ "classnames": "^2.5.1", "lodash": "^4.17.21", "luxon": "^3.4.4", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-helmet": "^6.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-helmet-async": "^2.0.5", "react-resize-detector": "^9.1.1", "uuid": "^9.0.1" } diff --git a/src/DetailsView/components/assessment-instance-table.tsx b/src/DetailsView/components/assessment-instance-table.tsx index 4221b444ad6..c3fd59b0136 100644 --- a/src/DetailsView/components/assessment-instance-table.tsx +++ b/src/DetailsView/components/assessment-instance-table.tsx @@ -14,7 +14,7 @@ import { AssessmentDefaultMessageGenerator } from 'assessments/assessment-defaul import { InstanceTableHeaderType, InstanceTableRow } from 'assessments/types/instance-table-data'; import { InsightsCommandButton } from 'common/components/controls/insights-command-button'; import { ManualTestStatus } from 'common/types/store-data/manual-test-status'; -import { has } from 'lodash'; +import { hasIn } from 'lodash'; import * as React from 'react'; import { AssessmentNavState, @@ -137,7 +137,7 @@ export class AssessmentInstanceTable extends React.Component - has(item.instance.testStepResults, step) && + hasIn(item.instance.testStepResults, step) && item.instance.testStepResults[step].status === ManualTestStatus.UNKNOWN, ); } diff --git a/src/DetailsView/details-view-initializer.ts b/src/DetailsView/details-view-initializer.ts index 3de773bb90b..1659240f78d 100644 --- a/src/DetailsView/details-view-initializer.ts +++ b/src/DetailsView/details-view-initializer.ts @@ -81,7 +81,7 @@ import { TabStopsFailedCounterIncludingNoInstance, TabStopsFailedCounterInstancesOnly, } from 'DetailsView/tab-stops-failed-counter'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { ReportExportServiceProviderImpl } from 'report-export/report-export-service-provider-impl'; import { AssessmentJsonExportGenerator } from 'reports/assessment-json-export-generator'; import { AssessmentReportHtmlGenerator } from 'reports/assessment-report-html-generator'; @@ -735,7 +735,7 @@ if (tabId != null) { const renderer = new DetailsViewRenderer( deps, dom, - ReactDOM.render, + ReactDOMClient.createRoot, documentElementSetter, ); @@ -751,7 +751,7 @@ if (tabId != null) { .catch(() => { const renderer = createNullifiedRenderer( document, - ReactDOM.render, + ReactDOMClient.createRoot, createDefaultLogger(), ); renderer.render(); @@ -760,7 +760,7 @@ if (tabId != null) { function createNullifiedRenderer( doc: Document, - render: typeof ReactDOM.render, + createRoot: typeof ReactDOMClient.createRoot, logger: Logger, ): NoContentAvailableViewRenderer { // using an instance of an actual store (instead of a StoreProxy) so we can get the default state. @@ -773,5 +773,5 @@ function createNullifiedRenderer( getNarrowModeThresholds: getNarrowModeThresholdsForWeb, }; - return new NoContentAvailableViewRenderer(deps, doc, render, documentElementSetter); + return new NoContentAvailableViewRenderer(deps, doc, createRoot, documentElementSetter); } diff --git a/src/DetailsView/details-view-renderer.tsx b/src/DetailsView/details-view-renderer.tsx index c241ce2ca7d..9873562059f 100644 --- a/src/DetailsView/details-view-renderer.tsx +++ b/src/DetailsView/details-view-renderer.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { Theme, ThemeDeps } from '../common/components/theme'; import { config } from '../common/configuration'; import { DocumentManipulator } from '../common/document-manipulator'; @@ -12,21 +12,20 @@ export class DetailsViewRenderer { constructor( private readonly deps: DetailsViewRendererDeps, private readonly dom: Document, - private readonly renderer: typeof ReactDOM.render, + private readonly createRoot: typeof ReactDOMClient.createRoot, private readonly documentManipulator: DocumentManipulator, ) {} public render(): void { - const detailsViewContainer = this.dom.querySelector('#details-container'); + const detailsViewContainer = this.dom.querySelector('#details-container') as Element; const iconPath = '../' + config.getOption('icon128'); this.documentManipulator.setShortcutIcon(iconPath); - - this.renderer( + const root = this.createRoot(detailsViewContainer); + root.render( <> , - detailsViewContainer, ); } } diff --git a/src/DetailsView/no-content-available-view-renderer.tsx b/src/DetailsView/no-content-available-view-renderer.tsx index 310af14edd1..5114ffc414d 100644 --- a/src/DetailsView/no-content-available-view-renderer.tsx +++ b/src/DetailsView/no-content-available-view-renderer.tsx @@ -6,22 +6,22 @@ import { NoContentAvailableViewDeps, } from 'DetailsView/components/no-content-available/no-content-available-view'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { config } from '../common/configuration'; export class NoContentAvailableViewRenderer { constructor( private readonly deps: NoContentAvailableViewDeps, private readonly dom: Document, - private readonly renderer: typeof ReactDOM.render, + private readonly createRoot: typeof ReactDOMClient.createRoot, private readonly documentManipulator: DocumentManipulator, ) {} public render(): void { - const detailsViewContainer = this.dom.querySelector('#details-container'); + const detailsViewContainer = this.dom.querySelector('#details-container') as Element; const iconPath = '../' + config.getOption('icon128'); this.documentManipulator.setShortcutIcon(iconPath); - - this.renderer(, detailsViewContainer); + const root = this.createRoot(detailsViewContainer); + root.render(); } } diff --git a/src/assessments/common/property-bag-column-renderer.tsx b/src/assessments/common/property-bag-column-renderer.tsx index ed521bd1751..2695b4c60aa 100644 --- a/src/assessments/common/property-bag-column-renderer.tsx +++ b/src/assessments/common/property-bag-column-renderer.tsx @@ -50,7 +50,8 @@ export function propertyBagColumnRenderer( propertyMap: DictionaryStringTo, ) => { if (isEmpty(propertyMap)) { - return {config.defaultValue}; + const value: any = config.defaultValue; + return {value}; } return Object.keys(propertyMap).map(key => { diff --git a/src/common/components/with-store-subscription.tsx b/src/common/components/with-store-subscription.tsx index 446254fad96..82e9f044577 100644 --- a/src/common/components/with-store-subscription.tsx +++ b/src/common/components/with-store-subscription.tsx @@ -14,7 +14,7 @@ export type WithStoreSubscriptionDeps = { }; export function withStoreSubscription

, S>( - WrappedComponent: React.ComponentType

, + WrappedComponent: React.ComponentType>, ): React.ComponentClass>, Partial> & { displayName: string; } { diff --git a/src/common/extensibility/react-extension-point.tsx b/src/common/extensibility/react-extension-point.tsx index 938865a3207..77f057164db 100644 --- a/src/common/extensibility/react-extension-point.tsx +++ b/src/common/extensibility/react-extension-point.tsx @@ -20,14 +20,14 @@ type ExtensionPoint = { apply: (component: C) => Extension; }; -type ReactExtension

= Extension> & { +type ReactExtension

= Extension>> & { extensionType: 'reactComponent'; }; -type ReactExtensionPoint

= ExtensionPoint> & { +type ReactExtensionPoint

= ExtensionPoint>> & { extensionType: 'reactComponent'; - create: (component: React.FC

) => ReactExtension

; - component: React.FC

; + create: (component: React.FC>) => ReactExtension

; + component: React.FC>; }; function isReactExtension(extension: Extension): extension is ReactExtension { @@ -55,7 +55,7 @@ export function reactExtensionPoint

( return result; }); - function create(extensionComponent: React.FC

): ReactExtension

{ + function create(extensionComponent: React.FC>): ReactExtension

{ const Wrap = extensionComponent; const wrapComponent = NamedFC

(extensionPointKey, props => ); wrapComponent.displayName = extensionPointKey; diff --git a/src/common/react/named-fc.ts b/src/common/react/named-fc.ts index ae9371169c8..508160c9e0b 100644 --- a/src/common/react/named-fc.ts +++ b/src/common/react/named-fc.ts @@ -2,11 +2,13 @@ // Licensed under the MIT License. import * as React from 'react'; -export type ReactFCWithDisplayName

= React.FC

& { displayName: string }; +export type ReactFCWithDisplayName

= React.FC> & { + displayName: string; +}; export function NamedFC

( displayName: string, - component: React.FC

, + component: React.FC>, ): ReactFCWithDisplayName

{ component.displayName = displayName; diff --git a/src/common/types/link-component-type.ts b/src/common/types/link-component-type.ts index 2029ba881f4..0f893e9c970 100644 --- a/src/common/types/link-component-type.ts +++ b/src/common/types/link-component-type.ts @@ -2,4 +2,4 @@ // Licensed under the MIT License. import { ILinkProps } from '@fluentui/react'; -export type LinkComponentType = React.FC; +export type LinkComponentType = React.FC>; diff --git a/src/debug-tools/initializer/debug-tools-init.tsx b/src/debug-tools/initializer/debug-tools-init.tsx index e17f0bebd39..35d26607370 100644 --- a/src/debug-tools/initializer/debug-tools-init.tsx +++ b/src/debug-tools/initializer/debug-tools-init.tsx @@ -30,7 +30,7 @@ import { defaultDateFormatter } from 'debug-tools/components/telemetry-viewer/te import { TelemetryListener } from 'debug-tools/controllers/telemetry-listener'; import { DebugToolsNavStore } from 'debug-tools/stores/debug-tools-nav-store'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import UAParser from 'ua-parser-js'; export const initializeDebugTools = () => { @@ -108,8 +108,8 @@ const createStoreProxies = (storeUpdateMessageHub: StoreUpdateMessageHub) => { const render = (deps: DebugToolsViewDeps) => { const container = document.querySelector('#debug-tools-container'); - - ReactDOM.render(, container); + const root = createRoot(container); + root.render(); }; initializeDebugTools(); diff --git a/src/injected/dialog-renderer-impl.tsx b/src/injected/dialog-renderer-impl.tsx index 0a129a9a2de..8a51edfb036 100644 --- a/src/injected/dialog-renderer-impl.tsx +++ b/src/injected/dialog-renderer-impl.tsx @@ -26,7 +26,7 @@ import { IssueFilingUrlStringUtils } from 'issue-filing/common/issue-filing-url- import { PlainTextFormatter } from 'issue-filing/common/markup/plain-text-formatter'; import { AxeResultToIssueFilingDataConverter } from 'issue-filing/rule-result-to-issue-filing-data'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { Target } from 'scanner/iruleresults'; import { DictionaryStringTo } from 'types/common-types'; import { rootContainerId } from './constants'; @@ -43,7 +43,7 @@ export class DialogRendererImpl implements DialogRenderer { constructor( private readonly dom: Document, - private readonly renderer: typeof ReactDOM.render, + private readonly createRoot: typeof ReactDOMClient.createRoot, private readonly frameMessenger: SingleFrameMessenger, private readonly htmlElementUtils: HTMLElementUtils, private readonly windowUtils: WindowUtils, @@ -101,8 +101,8 @@ export class DialogRendererImpl implements DialogRenderer { userConfigMessageCreator: mainWindowContext.getUserConfigMessageCreator(), LinkComponent: NewTabLink, }; - - this.renderer( + const root = this.createRoot(dialogContainer); + root.render( , - dialogContainer, ); return null; } else { diff --git a/src/injected/visualization/issues-formatter.ts b/src/injected/visualization/issues-formatter.ts index c3d1fd07fc5..3f60b35ddd2 100644 --- a/src/injected/visualization/issues-formatter.ts +++ b/src/injected/visualization/issues-formatter.ts @@ -5,7 +5,7 @@ import { NavigatorUtils } from 'common/navigator-utils'; import { HtmlElementAxeResults } from 'common/types/store-data/visualization-scan-result-data'; import { DialogRendererImpl } from 'injected/dialog-renderer-impl'; import { SingleFrameMessenger } from 'injected/frameCommunicators/single-frame-messenger'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { BrowserAdapter } from '../../common/browser-adapters/browser-adapter'; import { HTMLElementUtils } from '../../common/html-element-utils'; @@ -31,7 +31,7 @@ export class IssuesFormatter implements Formatter { ) { this.dialogRenderer = new DialogRendererImpl( document, - ReactDOM.render, + ReactDOMClient.createRoot, frameMessenger, htmlElementUtils, windowUtils, diff --git a/src/popup/components/diagnostic-view-toggle.tsx b/src/popup/components/diagnostic-view-toggle.tsx index 21d1efc2b0a..4e9687d19d4 100644 --- a/src/popup/components/diagnostic-view-toggle.tsx +++ b/src/popup/components/diagnostic-view-toggle.tsx @@ -46,7 +46,7 @@ export class DiagnosticViewToggle extends React.Component< // Must be consistent with internal react naming for same property to work // tslint:disable-next-line: variable-name - private _isMounted: boolean; + protected _isMounted: boolean; constructor(props: DiagnosticViewToggleProps) { super(props); diff --git a/src/popup/incompatible-browser-renderer.tsx b/src/popup/incompatible-browser-renderer.tsx index d13ac115fe2..c3772132657 100644 --- a/src/popup/incompatible-browser-renderer.tsx +++ b/src/popup/incompatible-browser-renderer.tsx @@ -2,21 +2,21 @@ // Licensed under the MIT License. import { title } from 'content/strings/application'; import * as React from 'react'; -import ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { NewTabLink } from '../common/components/new-tab-link'; import { Header } from './components/header'; export class IncompatibleBrowserRenderer { constructor( - private readonly renderer: typeof ReactDOM.render, + private readonly createRoot: typeof ReactDOMClient.createRoot, private readonly dom: Document, ) {} public render(): void { - const container = this.dom.querySelector('#popup-container'); - - this.renderer( + const container = this.dom.querySelector('#popup-container') as Element; + const root = this.createRoot(container); + root.render( <>

@@ -38,7 +38,6 @@ export class IncompatibleBrowserRenderer {
, - container, ); } } diff --git a/src/popup/main-renderer.tsx b/src/popup/main-renderer.tsx index 053a147bad5..b34f0fec8f6 100644 --- a/src/popup/main-renderer.tsx +++ b/src/popup/main-renderer.tsx @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { title } from 'content/strings/application'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import { Theme, ThemeDeps, ThemeInnerState } from '../common/components/theme'; import { WithStoreSubscriptionDeps } from '../common/components/with-store-subscription'; @@ -20,7 +20,7 @@ export class MainRenderer { constructor( private readonly deps: MainRendererDeps, private readonly popupHandlers: PopupHandlers, - private readonly renderer: typeof ReactDOM.render, + private readonly createRoot: typeof ReactDOMClient.createRoot, private readonly dom: Document, private readonly popupWindow: Window, private readonly targetTabUrl: string, @@ -31,9 +31,9 @@ export class MainRenderer { ) {} public render(): void { - const container = this.dom.querySelector('#popup-container'); - - this.renderer( + const container = this.dom.querySelector('#popup-container') as Element; + const root = this.createRoot(container); + root.render( <> , - container, ); } } diff --git a/src/popup/popup-initializer.ts b/src/popup/popup-initializer.ts index 9eefd313c44..3f1462b96fd 100644 --- a/src/popup/popup-initializer.ts +++ b/src/popup/popup-initializer.ts @@ -10,7 +10,7 @@ import { DocumentManipulator } from 'common/document-manipulator'; import { StoreUpdateMessageHub } from 'common/store-update-message-hub'; import { ExceptionTelemetryListener } from 'common/telemetry/exception-telemetry-listener'; import { ExceptionTelemetrySanitizer } from 'common/telemetry/exception-telemetry-sanitizer'; -import * as ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { AxeInfo } from '../common/axe-info'; import { NewTabLink } from '../common/components/new-tab-link'; import { DropdownClickHandler } from '../common/dropdown-click-handler'; @@ -81,10 +81,7 @@ export class PopupInitializer { } private useIncompatibleBrowserRenderer = () => { - const incompatibleBrowserRenderer = new IncompatibleBrowserRenderer( - ReactDOM.render, - document, - ); + const incompatibleBrowserRenderer = new IncompatibleBrowserRenderer(createRoot, document); incompatibleBrowserRenderer.render(); }; @@ -239,7 +236,7 @@ export class PopupInitializer { const renderer = new MainRenderer( deps, popupHandlers, - ReactDOM.render, + createRoot, document, window, tab.url, diff --git a/src/report-export/types/report-export-service.ts b/src/report-export/types/report-export-service.ts index 9825145b39c..445c27b8e50 100644 --- a/src/report-export/types/report-export-service.ts +++ b/src/report-export/types/report-export-service.ts @@ -25,5 +25,5 @@ export type ReportExportService = { href?: string, download?: string, ) => IContextualMenuItem; - exportForm?: React.ComponentType; + exportForm?: React.ComponentType>; }; diff --git a/src/reports/components/head.tsx b/src/reports/components/head.tsx index 331d42339d5..ffe811ef806 100644 --- a/src/reports/components/head.tsx +++ b/src/reports/components/head.tsx @@ -12,13 +12,12 @@ export type bundledStylesProp = { }; export const Head = NamedFC('Head', props => { + const titleValue = `${props.titlePreface} ${props.title}`; // tslint:disable: react-no-dangerous-html return ( - - {props.titlePreface} {props.title} - + {titleValue}