Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to React 19 #28674

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,13 @@
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
},
"resolutions": {
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001684",
"react": "19.0.0",
"react-dom": "19.0.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
},
Expand Down Expand Up @@ -135,10 +139,10 @@
"posthog-js": "1.157.2",
"qrcode": "1.5.4",
"re-resizable": "6.10.1",
"react": "^18.3.1",
"react": "^19",
"react-beautiful-dnd": "^13.1.0",
"react-blurhash": "^0.3.0",
"react-dom": "^18.3.1",
"react-dom": "^19",
"react-focus-lock": "^2.5.1",
"react-transition-group": "^4.4.1",
"rfc4648": "^1.4.0",
Expand Down Expand Up @@ -179,7 +183,7 @@
"@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/commonmark": "^0.27.4",
"@types/counterpart": "^0.18.1",
Expand All @@ -201,9 +205,9 @@
"@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "18.3.3",
"@types/react": "^19",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "18.3.1",
"@types/react-dom": "^19",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "2.13.0",
"@types/semver": "^7.5.8",
Expand Down
2 changes: 1 addition & 1 deletion src/@types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import { JSXElementConstructor } from "react";
import { JSXElementConstructor, type JSX } from "react";

export type { NonEmptyArray, XOR, Writeable } from "matrix-js-sdk/src/matrix";

Expand Down
11 changes: 8 additions & 3 deletions src/@types/react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import React, { PropsWithChildren } from "react";
declare module "react" {
// Fix forwardRef types for Generic components - https://stackoverflow.com/a/58473012
function forwardRef<T, P = {}>(
render: (props: PropsWithChildren<P>, ref: React.ForwardedRef<T>) => React.ReactElement | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
render: (props: PropsWithChildren<P>, ref: React.ForwardedRef<T>) => React.ReactElement<any> | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement<any> | null;

// Fix lazy types - https://stackoverflow.com/a/71017028
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;
// function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;

// Workaround for generics in React 19
interface FunctionComponent {
defaultProps?: {};
}
}
4 changes: 2 additions & 2 deletions src/HtmlUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { LegacyRef, ReactNode } from "react";
import React, { Ref, ReactNode, type JSX } from "react";
import sanitizeHtml from "sanitize-html";
import classNames from "classnames";
import katex from "katex";
Expand Down Expand Up @@ -503,7 +503,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
export function topicToHtml(
topic?: string,
htmlTopic?: string,
ref?: LegacyRef<HTMLSpanElement>,
ref?: Ref<HTMLSpanElement>,
allowExtendedHtml = false,
): ReactNode {
if (!SettingsStore.getValue("feature_html_topic")) {
Expand Down
2 changes: 1 addition & 1 deletion src/Linkify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export const sanitizeHtmlParams: IExtendedSanitizeOptions = {
};

/* Wrapper around linkify-react merging in our default linkify options */
export function Linkify({ as, options, children }: React.ComponentProps<typeof _Linkify>): ReactElement {
export function Linkify({ as, options, children }: React.ComponentProps<typeof _Linkify>): ReactElement<any> {
return (
<_Linkify as={as} options={merge({}, linkifyMatrixOptions, options)}>
{children}
Expand Down
15 changes: 8 additions & 7 deletions src/NodeAnimator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { Key, MutableRefObject, ReactElement, RefCallback } from "react";
import React, { HTMLAttributes, Key, MutableRefObject, ReactElement, RefCallback } from "react";

interface IChildProps {
style: React.CSSProperties;
Expand All @@ -23,7 +23,7 @@ interface IProps {
innerRef?: MutableRefObject<any>;
}

function isReactElement(c: ReturnType<(typeof React.Children)["toArray"]>[number]): c is ReactElement {
function isReactElement(c: ReturnType<(typeof React.Children)["toArray"]>[number]): c is ReactElement<any> {
return typeof c === "object" && "type" in c;
}

Expand All @@ -36,7 +36,7 @@ function isReactElement(c: ReturnType<(typeof React.Children)["toArray"]>[number
*/
export default class NodeAnimator extends React.Component<IProps> {
private nodes: Record<string, HTMLElement> = {};
private children: { [key: string]: ReactElement } = {};
private children: { [key: string]: ReactElement<any> } = {};
public static defaultProps: Partial<IProps> = {
startStyles: [],
};
Expand Down Expand Up @@ -68,21 +68,22 @@ export default class NodeAnimator extends React.Component<IProps> {
this.children = {};
React.Children.toArray(newChildren).forEach((c) => {
if (!isReactElement(c)) return;
const props = c.props as HTMLAttributes<HTMLElement>;
if (oldChildren[c.key!]) {
const old = oldChildren[c.key!];
const oldNode = this.nodes[old.key!];

if (oldNode && oldNode.style.left !== c.props.style.left) {
this.applyStyles(oldNode, { left: c.props.style.left });
if (oldNode && oldNode.style.left !== props.style!.left) {
this.applyStyles(oldNode, { left: props.style!.left });
}
// clone the old element with the props (and children) of the new element
// so prop updates are still received by the children.
this.children[c.key!] = React.cloneElement(old, c.props, c.props.children);
this.children[c.key!] = React.cloneElement(old, props, props.children);
} else {
// new element. If we have a startStyle, use that as the style and go through
// the enter animations
const newProps: Partial<IChildProps> = {};
const restingStyle = c.props.style;
const restingStyle = props.style!;

const startStyles = this.props.startStyles;
if (startStyles.length > 0) {
Expand Down
4 changes: 2 additions & 2 deletions src/accessibility/RovingTabIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
scrollIntoView,
onKeyDown,
}) => {
const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, {
const [state, dispatch] = useReducer(reducer, {
nodes: [],
});

Expand Down Expand Up @@ -354,7 +354,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
* nodeRef = inputRef when inputRef argument is provided.
*/
export const useRovingTabIndex = <T extends HTMLElement>(
inputRef?: RefObject<T>,
inputRef?: RefObject<T | null>,
): [FocusHandler, boolean, RefCallback<T>, RefObject<T | null>] => {
const context = useContext(RovingTabIndexContext);

Expand Down
6 changes: 3 additions & 3 deletions src/accessibility/context_menu/ContextMenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@

import AccessibleButton from "../../components/views/elements/AccessibleButton";

type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
type Props<T extends React.ElementType> = ComponentProps<typeof AccessibleButton<T>> & {

Check failure on line 15 in src/accessibility/context_menu/ContextMenuButton.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Type 'T' does not satisfy the constraint '"div"'.
label?: string;
// whether the context menu is currently open
isExpanded: boolean;
};

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
export const ContextMenuButton = forwardRef(function <T extends React.ElementType>(
{ label, isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElement>,
) {
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
element={element as React.ElementType}

Check failure on line 29 in src/accessibility/context_menu/ContextMenuButton.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Type 'ElementType<any, keyof IntrinsicElements>' is not assignable to type '"div" | undefined'.
onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-label={label}
Expand Down
6 changes: 3 additions & 3 deletions src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ import React, { ComponentProps, forwardRef, Ref } from "react";

import AccessibleButton from "../../components/views/elements/AccessibleButton";

type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
type Props<T extends React.ElementType> = ComponentProps<typeof AccessibleButton<T>> & {
// whether the context menu is currently open
isExpanded: boolean;
};

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
export const ContextMenuTooltipButton = forwardRef(function <T extends React.ElementType>(
{ isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElement>,
) {
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
element={element as React.ElementType}
onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-haspopup={true}
Expand Down
12 changes: 8 additions & 4 deletions src/accessibility/context_menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import React, { type JSX } from "react";

import { RovingAccessibleButton } from "../RovingTabIndex";

interface IProps extends React.ComponentProps<typeof RovingAccessibleButton> {
type IProps<T extends keyof JSX.IntrinsicElements> = React.ComponentProps<typeof RovingAccessibleButton<T>> & {
label?: string;
}
};

// Semantic component for representing a role=menuitem
export const MenuItem: React.FC<IProps> = ({ children, label, ...props }) => {
export const MenuItem = <T extends keyof JSX.IntrinsicElements>({
children,
label,
...props
}: IProps<T>): JSX.Element => {
const ariaLabel = props["aria-label"] || label;

return (
Expand Down
11 changes: 4 additions & 7 deletions src/accessibility/roving/RovingAccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { ComponentProps } from "react";
import React, { ComponentProps, type JSX } from "react";

import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";

type Props<T extends keyof JSX.IntrinsicElements> = Omit<
ComponentProps<typeof AccessibleButton<T>>,
"inputRef" | "tabIndex"
> & {
type Props<T extends React.ElementType> = Omit<ComponentProps<typeof AccessibleButton<T>>, "inputRef" | "tabIndex"> & {
inputRef?: Ref;
focusOnMouseOver?: boolean;
};

// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
export const RovingAccessibleButton = <T extends React.ElementType>({
inputRef,
onFocus,
onMouseOver,
Expand All @@ -33,7 +30,7 @@ export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
element={element as React.ElementType}
onFocus={(event: React.FocusEvent) => {
onFocusInternal();
onFocus?.(event);
Expand Down
2 changes: 1 addition & 1 deletion src/accessibility/roving/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ Please see LICENSE files in the repository root for full details.

import { RefObject } from "react";

export type Ref = RefObject<HTMLElement>;
export type Ref = RefObject<HTMLElement | null>;

export type FocusHandler = () => void;
2 changes: 1 addition & 1 deletion src/async-components/structures/ErrorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { ReactNode } from "react";
import React, { ReactNode, type JSX } from "react";
import { Text, Heading, Button, Separator } from "@vector-im/compound-web";
import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import React, { type JSX } from "react";
import { logger } from "matrix-js-sdk/src/logger";

import { MatrixClientPeg } from "../../../../MatrixClientPeg";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { createRef } from "react";
import React, { createRef, type JSX } from "react";
import FileSaver from "file-saver";
import { logger } from "matrix-js-sdk/src/logger";
import { AuthDict, CrossSigningKeys, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
type="password"
disabled={disableForm}
autoComplete="new-password"
fieldRef={(field) => (this.fieldPassword = field)}
fieldRef={(field) => {
this.fieldPassword = field;
}}
/>
</div>
<div className="mx_E2eKeysDialog_inputRow">
Expand All @@ -195,7 +197,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
type="password"
disabled={disableForm}
autoComplete="new-password"
fieldRef={(field) => (this.fieldPasswordConfirm = field)}
fieldRef={(field) => {
this.fieldPasswordConfirm = field;
}}
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/autocomplete/Autocompleter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface ICompletion {
type?: "at-room" | "command" | "community" | "room" | "user";
completion: string;
completionId?: string;
component: ReactElement;
component: ReactElement<any>;
range: ISelectionRange;
command?: string;
suffix?: string;
Expand Down
21 changes: 6 additions & 15 deletions src/components/structures/AutoHideScrollbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,20 @@ Please see LICENSE files in the repository root for full details.
*/

import classNames from "classnames";
import React, { HTMLAttributes, ReactHTML, ReactNode, WheelEvent } from "react";
import React, { ReactNode, WheelEvent } from "react";

type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> =
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<Omit<JSX.IntrinsicElements[T], "ref">>;

export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElementProps<T>, "onScroll"> & {
element: T;
export type IProps<T extends React.ElementType> = React.ComponentPropsWithoutRef<T> & {
element?: T;
className?: string;
onScroll?: (event: Event) => void;
onWheel?: (event: WheelEvent) => void;
style?: React.CSSProperties;
tabIndex?: number;
wrappedRef?: (ref: HTMLDivElement | null) => void;
children: ReactNode;
};

export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> extends React.Component<IProps<T>> {
public static defaultProps = {
element: "div" as keyof ReactHTML,
};

public readonly containerRef: React.RefObject<HTMLDivElement> = React.createRef();
export default class AutoHideScrollbar<T extends React.ElementType> extends React.Component<IProps<T>> {
public readonly containerRef: React.RefObject<HTMLDivElement | null> = React.createRef();

public componentDidMount(): void {
if (this.containerRef.current && this.props.onScroll) {
Expand All @@ -55,7 +46,7 @@ export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> ex
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;

return React.createElement(
element,
element ?? "div",
{
...otherProps,
ref: this.containerRef,
Expand Down
Loading
Loading