Skip to content

Commit e1b2e3a

Browse files
Update react monorepo to v19 (major) (#28914)
* Update react monorepo to v19 * Import JSX explicitly for React 19 compatibility Signed-off-by: Michael Telatynski <[email protected]> * Update usages of refs for React 19 compatibility Signed-off-by: Michael Telatynski <[email protected]> * Update react imports Signed-off-by: Michael Telatynski <[email protected]> * Avoid legacy contexts as much as possible Signed-off-by: Michael Telatynski <[email protected]> * Avoid deprecated React symbols Signed-off-by: Michael Telatynski <[email protected]> * Stash Signed-off-by: Michael Telatynski <[email protected]> * Update usages of refs for React 19 compatibility Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Switch pillify to use a html-react-parser approach rather than DOM muddling Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate react html parsing Signed-off-by: Michael Telatynski <[email protected]> * Iterate react html parsing Signed-off-by: Michael Telatynski <[email protected]> * Iterate html parsing Signed-off-by: Michael Telatynski <[email protected]> * Memoize the EventContentBody component Signed-off-by: Michael Telatynski <[email protected]> * Iterate html parsing Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Simplify Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Discard changes to src/Linkify.tsx * Discard changes to src/components/views/messages/TextualBody.tsx * Discard changes to src/settings/handlers/AbstractLocalStorageSettingsHandler.ts * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Prepare for React 19 upgrade Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Remove stale comment Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <[email protected]>
1 parent f54fbf7 commit e1b2e3a

Some content is hidden

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

44 files changed

+297
-287
lines changed

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@
6969
"postinstall": "patch-package"
7070
},
7171
"resolutions": {
72+
"**/pretty-format/react-is": "19.0.0",
7273
"@playwright/test": "1.51.1",
73-
"@types/react": "18.3.18",
74-
"@types/react-dom": "18.3.5",
74+
"@types/react": "19.0.10",
75+
"@types/react-dom": "19.0.4",
7576
"oidc-client-ts": "3.2.0",
7677
"jwt-decode": "4.0.0",
7778
"caniuse-lite": "1.0.30001707",
@@ -141,10 +142,10 @@
141142
"posthog-js": "1.157.2",
142143
"qrcode": "1.5.4",
143144
"re-resizable": "6.11.2",
144-
"react": "^18.3.1",
145+
"react": "^19.0.0",
145146
"react-beautiful-dnd": "^13.1.0",
146147
"react-blurhash": "^0.3.0",
147-
"react-dom": "^18.3.1",
148+
"react-dom": "^19.0.0",
148149
"react-focus-lock": "^2.5.1",
149150
"react-string-replace": "^1.1.1",
150151
"react-transition-group": "^4.4.1",
@@ -211,9 +212,9 @@
211212
"@types/node-fetch": "^2.6.2",
212213
"@types/pako": "^2.0.0",
213214
"@types/qrcode": "^1.3.5",
214-
"@types/react": "18.3.18",
215+
"@types/react": "19.0.10",
215216
"@types/react-beautiful-dnd": "^13.0.0",
216-
"@types/react-dom": "18.3.5",
217+
"@types/react-dom": "19.0.4",
217218
"@types/react-transition-group": "^4.4.0",
218219
"@types/sanitize-html": "2.15.0",
219220
"@types/semver": "^7.5.8",

patches/@types+react+18.3.18.patch

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

patches/@types+react+19.0.10.patch

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
diff --git a/node_modules/@types/react/index.d.ts b/node_modules/@types/react/index.d.ts
2+
index 2272032..18bd20a 100644
3+
--- a/node_modules/@types/react/index.d.ts
4+
+++ b/node_modules/@types/react/index.d.ts
5+
@@ -134,7 +134,7 @@ declare namespace React {
6+
props: P,
7+
) => ReactNode | Promise<ReactNode>)
8+
// constructor signature must match React.Component
9+
- | (new(props: P) => Component<any, any>);
10+
+ | (new(props: P, context?: any) => Component<any, any>);
11+
12+
/**
13+
* Created by {@link createRef}, or {@link useRef} when passed `null`.
14+
@@ -941,7 +941,7 @@ declare namespace React {
15+
context: unknown;
16+
17+
// Keep in sync with constructor signature of JSXElementConstructor and ComponentClass.
18+
- constructor(props: P);
19+
+ constructor(props: P, context?: unknown);
20+
21+
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
22+
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
23+
@@ -1113,7 +1113,7 @@ declare namespace React {
24+
*/
25+
interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
26+
// constructor signature must match React.Component
27+
- new(props: P): Component<P, S>;
28+
+ new(props: P, context?: any): Component<P, S>;
29+
/**
30+
* Ignored by React.
31+
* @deprecated Only kept in types for backwards compatibility. Will be removed in a future major release.

patches/react-blurhash+0.3.0.patch

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
diff --git a/node_modules/react-blurhash/dist/index.d.ts b/node_modules/react-blurhash/dist/index.d.ts
2+
index 3adbd0a..32e8c13 100644
3+
--- a/node_modules/react-blurhash/dist/index.d.ts
4+
+++ b/node_modules/react-blurhash/dist/index.d.ts
5+
@@ -19,7 +19,7 @@ declare class Blurhash extends React.PureComponent<Props$1> {
6+
resolutionY: number;
7+
};
8+
componentDidUpdate(): void;
9+
- render(): JSX.Element;
10+
+ render(): React.JSX.Element;
11+
}
12+
13+
declare type Props = React.CanvasHTMLAttributes<HTMLCanvasElement> & {
14+
@@ -37,7 +37,7 @@ declare class BlurhashCanvas extends React.PureComponent<Props> {
15+
componentDidUpdate(): void;
16+
handleRef: (canvas: HTMLCanvasElement) => void;
17+
draw: () => void;
18+
- render(): JSX.Element;
19+
+ render(): React.JSX.Element;
20+
}
21+
22+
export { Blurhash, BlurhashCanvas };

playwright/e2e/regression-tests/pills-click-in-app.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ test.describe("Pills", () => {
4343

4444
// go back to the message room and try to click on the pill text, as a user would
4545
await app.viewRoomByName(messageRoom);
46+
await expect(page).toHaveURL(new RegExp(`/#/room/${messageRoomId}`));
4647
const pillText = page.locator(".mx_EventTile_body .mx_Pill .mx_Pill_text");
4748
await expect(pillText).toHaveCSS("pointer-events", "none");
4849
await pillText.click({ force: true }); // force is to ensure we bypass pointer-events

src/@types/react.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ declare module "react" {
1818

1919
// Fix lazy types - https://stackoverflow.com/a/71017028
2020
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;
21+
22+
// Standardize defaultProps for FunctionComponent so we can write generics assuming `defaultProps` exists on ComponentType
23+
interface FunctionComponent {
24+
defaultProps?: unknown;
25+
}
2126
}

src/HtmlUtils.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
99
Please see LICENSE files in the repository root for full details.
1010
*/
1111

12-
import React, { type JSX, type LegacyRef, type ReactNode } from "react";
12+
import React, { type JSX, type Key, type LegacyRef, type ReactNode } from "react";
1313
import sanitizeHtml, { type IOptions } from "sanitize-html";
1414
import classNames from "classnames";
1515
import katex from "katex";
@@ -239,7 +239,7 @@ class HtmlHighlighter extends BaseHighlighter<string> {
239239

240240
const emojiToHtmlSpan = (emoji: string): string =>
241241
`<span class='mx_Emoji' title='${unicodeToShortcode(emoji)}'>${emoji}</span>`;
242-
const emojiToJsxSpan = (emoji: string, key: number): JSX.Element => (
242+
const emojiToJsxSpan = (emoji: string, key: Key): JSX.Element => (
243243
<span key={key} className="mx_Emoji" title={unicodeToShortcode(emoji)}>
244244
{emoji}
245245
</span>

src/Lifecycle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
321321
} catch (error) {
322322
logger.error("Failed to login via OIDC", error);
323323

324-
await onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
324+
onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
325325
return false;
326326
}
327327
}
@@ -468,7 +468,7 @@ type TryAgainFunction = () => void;
468468
* @param description error description
469469
* @param tryAgain OPTIONAL function to call on try again button from error dialog
470470
*/
471-
async function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): Promise<void> {
471+
function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): void {
472472
Modal.createDialog(ErrorDialog, {
473473
title: _t("auth|oidc|error_title"),
474474
description,

src/accessibility/RovingTabIndex.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
212212
scrollIntoView,
213213
onKeyDown,
214214
}) => {
215-
const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, {
215+
const [state, dispatch] = useReducer<IState, [Action]>(reducer, {
216216
nodes: [],
217217
});
218218

src/autocomplete/Autocompleter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import { type ReactElement } from "react";
9+
import { type ReactElement, type RefAttributes, type HTMLAttributes } from "react";
1010
import { type Room } from "matrix-js-sdk/src/matrix";
1111

1212
import CommandProvider from "./CommandProvider";
@@ -31,7 +31,7 @@ export interface ICompletion {
3131
type?: "at-room" | "command" | "community" | "room" | "user";
3232
completion: string;
3333
completionId?: string;
34-
component: ReactElement;
34+
component: ReactElement<RefAttributes<HTMLElement> & HTMLAttributes<HTMLElement>>;
3535
range: ISelectionRange;
3636
command?: string;
3737
suffix?: string;

src/components/structures/IndicatorScrollbar.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollb
2121
// scroll horizontally rather than vertically. This should only be used on components
2222
// with no vertical scroll opportunity.
2323
verticalScrollsHorizontally?: boolean;
24-
25-
children: React.ReactNode;
2624
};
2725

2826
interface IState {

src/components/structures/MatrixChat.tsx

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,6 @@ interface IProps {
165165
initialScreenAfterLogin?: IScreen;
166166
// displayname, if any, to set on the device when logging in/registering.
167167
defaultDeviceDisplayName?: string;
168-
169-
// Used by tests, this function is called when session initialisation starts
170-
// with a promise that resolves or rejects once the initialiation process
171-
// has finished, so that tests can wait for this to avoid them executing over
172-
// each other.
173-
initPromiseCallback?: (p: Promise<void>) => void;
174168
}
175169

176170
interface IState {
@@ -291,9 +285,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
291285
*/
292286
private startInitSession = (): void => {
293287
const initProm = this.initSession();
294-
if (this.props.initPromiseCallback) {
295-
this.props.initPromiseCallback(initProm);
296-
}
297288

298289
initProm.catch((err) => {
299290
// TODO: show an error screen, rather than a spinner of doom
@@ -1002,10 +993,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
1002993
// Wait for the first sync to complete so that if a room does have an alias,
1003994
// it would have been retrieved.
1004995
if (!this.firstSyncComplete) {
1005-
if (!this.firstSyncPromise) {
1006-
logger.warn("Cannot view a room before first sync. room_id:", roomInfo.room_id);
1007-
return;
1008-
}
1009996
await this.firstSyncPromise.promise;
1010997
}
1011998

@@ -1116,8 +1103,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
11161103
private viewUser(userId: string, subAction: string): void {
11171104
// Wait for the first sync so that `getRoom` gives us a room object if it's
11181105
// in the sync response
1119-
const waitForSync = this.firstSyncPromise ? this.firstSyncPromise.promise : Promise.resolve();
1120-
waitForSync.then(() => {
1106+
this.firstSyncPromise.promise.then(() => {
11211107
if (subAction === "chat") {
11221108
this.chatCreateOrReuse(userId);
11231109
return;
@@ -1480,11 +1466,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
14801466
* (useful for setting listeners)
14811467
*/
14821468
private onWillStartClient(): void {
1483-
// reset the 'have completed first sync' flag,
1484-
// since we're about to start the client and therefore about
1485-
// to do the first sync
1469+
// Reset the 'have completed first sync' flag,
1470+
// since we're about to start the client and therefore about to do the first sync
1471+
// We resolve the existing promise with the new one to update any existing listeners
1472+
if (!this.firstSyncComplete) {
1473+
const firstSyncPromise = defer<void>();
1474+
this.firstSyncPromise.resolve(firstSyncPromise.promise);
1475+
this.firstSyncPromise = firstSyncPromise;
1476+
} else {
1477+
this.firstSyncPromise = defer();
1478+
}
14861479
this.firstSyncComplete = false;
1487-
this.firstSyncPromise = defer();
14881480
const cli = MatrixClientPeg.safeGet();
14891481

14901482
// Allow the JS SDK to reap timeline events. This reduces the amount of

src/components/structures/TabbedView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ export class Tab<T extends string> {
2727
* @param {string} id The tab's ID.
2828
* @param {string} label The untranslated tab label.
2929
* @param {string|JSX.Element} icon An SVG element to use for the tab icon. Can also be a string for legacy icons, in which case it is the class for the tab icon. This should be a simple mask.
30-
* @param {React.ReactNode} body The JSX for the tab container.
30+
* @param {JSX.Element} body The JSX for the tab container.
3131
* @param {string} screenName The screen name to report to Posthog.
3232
*/
3333
public constructor(
3434
public readonly id: T,
3535
public readonly label: TranslationKey,
3636
public readonly icon: string | JSX.Element | null,
37-
public readonly body: React.ReactNode,
37+
public readonly body: JSX.Element,
3838
public readonly screenName?: ScreenName,
3939
) {}
4040
}

src/components/structures/auth/header/AuthHeaderContext.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import { createContext, type Dispatch, type ReducerAction, type ReducerState } from "react";
9+
import { createContext, type Dispatch, type Reducer, type ReducerState } from "react";
1010

1111
import type { AuthHeaderReducer } from "./AuthHeaderProvider";
1212

13+
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
14+
1315
interface AuthHeaderContextType {
1416
state: ReducerState<AuthHeaderReducer>;
1517
dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>;

src/components/structures/auth/header/AuthHeaderProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface AuthHeaderAction {
2525
export type AuthHeaderReducer = Reducer<ComponentProps<typeof AuthHeaderModifier>[], AuthHeaderAction>;
2626

2727
export function AuthHeaderProvider({ children }: PropsWithChildren): JSX.Element {
28-
const [state, dispatch] = useReducer<AuthHeaderReducer>(
28+
const [state, dispatch] = useReducer<ComponentProps<typeof AuthHeaderModifier>[], [AuthHeaderAction]>(
2929
(state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => {
3030
switch (action.type) {
3131
case AuthHeaderActionType.Add:

src/components/views/avatars/MemberAvatar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CardContext } from "../right_panel/context";
1818
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
1919
import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
2020
import { _t } from "../../../languageHandler";
21+
import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
2122

2223
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
2324
member: RoomMember | null;
@@ -47,6 +48,7 @@ function MemberAvatar(
4748
}: IProps,
4849
ref: Ref<HTMLElement>,
4950
): JSX.Element {
51+
const cli = useContext(MatrixClientContext);
5052
const card = useContext(CardContext);
5153

5254
const member = useRoomMemberProfile({
@@ -60,7 +62,7 @@ function MemberAvatar(
6062
let imageUrl: string | null | undefined;
6163
if (member?.name) {
6264
if (member.getMxcAvatarUrl()) {
63-
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(
65+
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "", cli).getThumbnailOfSourceHttp(
6466
parseInt(size, 10),
6567
parseInt(size, 10),
6668
resizeMethod,

0 commit comments

Comments
 (0)