Preparations for React 18 (#12860)

* Add missing types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Eagerly add `children` to props in prep for React 18

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Avoid assuming that setState immediately sets `this.state` values

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add missing context declaration

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix UserFriendlyError types to work with React 18

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-08-05 08:59:27 +01:00 committed by GitHub
parent accbe07439
commit 090586439f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 63 additions and 44 deletions

View file

@ -97,6 +97,12 @@ type WithKeyFunction<T> = T extends Key
toKey: (key: T) => Key; toKey: (key: T) => Key;
}; };
export interface AdditionalOptionsProps {
menuDisplayed: boolean;
closeMenu: () => void;
openMenu: () => void;
}
type IProps<T> = WithKeyFunction<T> & { type IProps<T> = WithKeyFunction<T> & {
value: T; value: T;
options: readonly GenericDropdownMenuOption<T>[] | readonly GenericDropdownMenuGroup<T>[]; options: readonly GenericDropdownMenuOption<T>[] | readonly GenericDropdownMenuGroup<T>[];
@ -105,11 +111,7 @@ type IProps<T> = WithKeyFunction<T> & {
onOpen?: (ev: ButtonEvent) => void; onOpen?: (ev: ButtonEvent) => void;
onClose?: (ev: ButtonEvent) => void; onClose?: (ev: ButtonEvent) => void;
className?: string; className?: string;
AdditionalOptions?: FunctionComponent<{ AdditionalOptions?: FunctionComponent<AdditionalOptionsProps>;
menuDisplayed: boolean;
closeMenu: () => void;
openMenu: () => void;
}>;
}; };
export function GenericDropdownMenu<T>({ export function GenericDropdownMenu<T>({

View file

@ -117,7 +117,7 @@ export const ThreadPanelHeader: React.FC<{
) : null; ) : null;
const onMarkAllThreadsReadClick = React.useCallback( const onMarkAllThreadsReadClick = React.useCallback(
(e) => { (e: React.MouseEvent) => {
PosthogTrackers.trackInteraction("WebThreadsMarkAllReadButton", e); PosthogTrackers.trackInteraction("WebThreadsMarkAllReadButton", e);
if (!roomContext.room) { if (!roomContext.room) {
logger.error("No room in context to mark all threads read"); logger.error("No room in context to mark all threads read");

View file

@ -248,15 +248,20 @@ export default class Registration extends React.Component<IProps, IState> {
logger.error("Failed to get login flows to check for SSO support", e); logger.error("Failed to get login flows to check for SSO support", e);
} }
this.setState(({ flows }) => ({ await new Promise<void>((resolve) => {
matrixClient: cli, this.setState(
ssoFlow, ({ flows }) => ({
oidcNativeFlow, matrixClient: cli,
// if we are using oidc native we won't continue with flow discovery on HS ssoFlow,
// so set an empty array to indicate flows are no longer loading oidcNativeFlow,
flows: oidcNativeFlow ? [] : flows, // if we are using oidc native we won't continue with flow discovery on HS
busy: false, // so set an empty array to indicate flows are no longer loading
})); flows: oidcNativeFlow ? [] : flows,
busy: false,
}),
resolve,
);
});
// don't need to check with homeserver for login flows // don't need to check with homeserver for login flows
// since we are going to use OIDC native flow // since we are going to use OIDC native flow

View file

@ -19,7 +19,7 @@ limitations under the License.
import React, { forwardRef, useCallback, useContext, useEffect, useState } from "react"; import React, { forwardRef, useCallback, useContext, useEffect, useState } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { ClientEvent } from "matrix-js-sdk/src/matrix"; import { ClientEvent, SyncState } from "matrix-js-sdk/src/matrix";
import { Avatar } from "@vector-im/compound-web"; import { Avatar } from "@vector-im/compound-web";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -80,7 +80,7 @@ const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [
}, [url, JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps }, [url, JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const onClientSync = useCallback((syncState, prevState) => { const onClientSync = useCallback((syncState: SyncState, prevState: SyncState | null) => {
// Consider the client reconnected if there is no error with syncing. // Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP. // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
const reconnected = syncState !== "ERROR" && prevState !== syncState; const reconnected = syncState !== "ERROR" && prevState !== syncState;

View file

@ -26,6 +26,7 @@ interface OptionProps {
id?: string; id?: string;
className?: string; className?: string;
onClick: ((ev: ButtonEvent) => void) | null; onClick: ((ev: ButtonEvent) => void) | null;
children?: ReactNode;
} }
export const Option: React.FC<OptionProps> = ({ inputRef, children, endAdornment, className, ...props }) => { export const Option: React.FC<OptionProps> = ({ inputRef, children, endAdornment, className, ...props }) => {

View file

@ -26,7 +26,11 @@ import SdkConfig from "../../../SdkConfig";
import { SettingLevel } from "../../../settings/SettingLevel"; import { SettingLevel } from "../../../settings/SettingLevel";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { Protocols } from "../../../utils/DirectoryUtils"; import { Protocols } from "../../../utils/DirectoryUtils";
import { GenericDropdownMenu, GenericDropdownMenuItem } from "../../structures/GenericDropdownMenu"; import {
AdditionalOptionsProps,
GenericDropdownMenu,
GenericDropdownMenuItem,
} from "../../structures/GenericDropdownMenu";
import TextInputDialog from "../dialogs/TextInputDialog"; import TextInputDialog from "../dialogs/TextInputDialog";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import withValidation from "../elements/Validation"; import withValidation from "../elements/Validation";
@ -181,7 +185,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
})); }));
const addNewServer = useCallback( const addNewServer = useCallback(
({ closeMenu }) => ( ({ closeMenu }: AdditionalOptionsProps) => (
<> <>
<span className="mx_GenericDropdownMenu_divider" /> <span className="mx_GenericDropdownMenu_divider" />
<MenuItemRadio <MenuItemRadio

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ContextType, CSSProperties, MutableRefObject } from "react"; import React, { ContextType, CSSProperties, MutableRefObject, ReactNode } from "react";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import WidgetUtils from "../../../utils/WidgetUtils"; import WidgetUtils from "../../../utils/WidgetUtils";
@ -28,6 +28,7 @@ interface IProps {
persistentRoomId: string; persistentRoomId: string;
pointerEvents?: CSSProperties["pointerEvents"]; pointerEvents?: CSSProperties["pointerEvents"];
movePersistedElement: MutableRefObject<(() => void) | undefined>; movePersistedElement: MutableRefObject<(() => void) | undefined>;
children?: ReactNode;
} }
export default class PersistentApp extends React.Component<IProps> { export default class PersistentApp extends React.Component<IProps> {

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, { useCallback, useContext } from "react"; import React, { useCallback, useContext } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, Room, RoomState } from "matrix-js-sdk/src/matrix";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
@ -52,7 +52,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
const predecessor = useRoomState( const predecessor = useRoomState(
roomContext.room, roomContext.room,
useCallback( useCallback(
(state) => state.findPredecessor(msc3946ProcessDynamicPredecessor), (state: RoomState) => state.findPredecessor(msc3946ProcessDynamicPredecessor),
[msc3946ProcessDynamicPredecessor], [msc3946ProcessDynamicPredecessor],
), ),
); );
@ -63,9 +63,9 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
event_id: predecessor.eventId, event_id: predecessor?.eventId,
highlighted: true, highlighted: true,
room_id: predecessor.roomId, room_id: predecessor?.roomId,
metricsTrigger: "Predecessor", metricsTrigger: "Predecessor",
metricsViaKeyboard: e.type !== "click", metricsViaKeyboard: e.type !== "click",
}); });
@ -126,7 +126,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
const predecessorPermalink = prevRoom const predecessorPermalink = prevRoom
? createLinkWithRoom(prevRoom, predecessor.roomId, predecessor.eventId) ? createLinkWithRoom(prevRoom, predecessor.roomId, predecessor.eventId)
: createLinkWithoutRoom(predecessor.roomId, predecessor.viaServers, predecessor.eventId); : createLinkWithoutRoom(predecessor.roomId, predecessor?.viaServers ?? [], predecessor.eventId);
const link = ( const link = (
<a href={predecessorPermalink} onClick={onLinkClicked}> <a href={predecessorPermalink} onClick={onLinkClicked}>

View file

@ -27,6 +27,7 @@ interface IProps {
export default class TextualEvent extends React.Component<IProps> { export default class TextualEvent extends React.Component<IProps> {
public static contextType = RoomContext; public static contextType = RoomContext;
public declare context: React.ContextType<typeof RoomContext>;
public render(): React.ReactNode { public render(): React.ReactNode {
const text = TextForEvent.textForEvent( const text = TextForEvent.textForEvent(

View file

@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { ReactNode } from "react";
interface Props { interface Props {
label: string; label: string;
children?: ReactNode;
} }
export const enum SeparatorKind { export const enum SeparatorKind {

View file

@ -34,6 +34,7 @@ import { WidgetType } from "../../../widgets/WidgetType";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
import WidgetUtils from "../../../utils/WidgetUtils"; import WidgetUtils from "../../../utils/WidgetUtils";
import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions"; import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions";
import { ButtonEvent } from "../elements/AccessibleButton";
interface Props { interface Props {
widgetId: string; widgetId: string;
@ -62,7 +63,7 @@ export const WidgetPip: FC<Props> = ({ widgetId, room, viewingRoom, onStartMovin
const call = useCallForWidget(widgetId, room.roomId); const call = useCallForWidget(widgetId, room.roomId);
const onBackClick = useCallback( const onBackClick = useCallback(
(ev) => { (ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@ -87,7 +88,7 @@ export const WidgetPip: FC<Props> = ({ widgetId, room, viewingRoom, onStartMovin
); );
const onLeaveClick = useCallback( const onLeaveClick = useCallback(
(ev) => { (ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();

View file

@ -424,6 +424,7 @@ export const UserOptionsSection: React.FC<{
member: Member; member: Member;
canInvite: boolean; canInvite: boolean;
isSpace?: boolean; isSpace?: boolean;
children?: ReactNode;
}> = ({ member, canInvite, isSpace, children }) => { }> = ({ member, canInvite, isSpace, children }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
@ -1036,7 +1037,7 @@ const IgnoreToggleButton: React.FC<{
}, [cli, member.userId]); }, [cli, member.userId]);
// Recheck also if we receive new accountData m.ignored_user_list // Recheck also if we receive new accountData m.ignored_user_list
const accountDataHandler = useCallback( const accountDataHandler = useCallback(
(ev) => { (ev: MatrixEvent) => {
if (ev.getType() === "m.ignored_user_list") { if (ev.getType() === "m.ignored_user_list") {
setIsIgnored(cli.isUserIgnored(member.userId)); setIsIgnored(cli.isUserIgnored(member.userId));
} }

View file

@ -116,7 +116,10 @@ export default function RoomHeader({
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join"); const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
const videoClick = useCallback((ev) => videoCallClick(ev, callOptions[0]), [callOptions, videoCallClick]); const videoClick = useCallback(
(ev: React.MouseEvent) => videoCallClick(ev, callOptions[0]),
[callOptions, videoCallClick],
);
const toggleCallButton = ( const toggleCallButton = (
<Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}> <Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}>

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"; import React, { ChangeEvent, ReactNode, useCallback, useEffect, useMemo, useState } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web"; import { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web";
import { Icon as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg"; import { Icon as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg";
@ -37,7 +37,7 @@ import Modal from "../../../Modal";
import defaultDispatcher from "../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Flex } from "../../utils/Flex"; import { Flex } from "../../utils/Flex";
const SpinnerToast: React.FC = ({ children }) => ( const SpinnerToast: React.FC<{ children?: ReactNode }> = ({ children }) => (
<> <>
<InlineSpinner /> <InlineSpinner />
{children} {children}

View file

@ -45,7 +45,7 @@ const QuickSettingsButton: React.FC<{
useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces"); useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces");
const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const developerModeEnabled = useSettingValue("developerMode"); const developerModeEnabled = useSettingValue<boolean>("developerMode");
let contextMenu: JSX.Element | undefined; let contextMenu: JSX.Element | undefined;
if (menuDisplayed && handle.current) { if (menuDisplayed && handle.current) {

View file

@ -26,9 +26,9 @@ export const useAccountData = <T extends {}>(cli: MatrixClient, eventType: strin
const [value, setValue] = useState<T | undefined>(() => tryGetContent<T>(cli.getAccountData(eventType))); const [value, setValue] = useState<T | undefined>(() => tryGetContent<T>(cli.getAccountData(eventType)));
const handler = useCallback( const handler = useCallback(
(event) => { (event: MatrixEvent) => {
if (event.getType() !== eventType) return; if (event.getType() !== eventType) return;
setValue(event.getContent()); setValue(event.getContent<T>());
}, },
[eventType], [eventType],
); );

View file

@ -53,7 +53,7 @@ const FALLBACK_LOCALE = "en";
counterpart.setFallbackLocale(FALLBACK_LOCALE); counterpart.setFallbackLocale(FALLBACK_LOCALE);
export interface ErrorOptions { export interface ErrorOptions {
// Because we're mixing the subsitution variables and `cause` into the same object // Because we're mixing the substitution variables and `cause` into the same object
// below, we want them to always explicitly say whether there is an underlying error // below, we want them to always explicitly say whether there is an underlying error
// or not to avoid typos of "cause" slipping through unnoticed. // or not to avoid typos of "cause" slipping through unnoticed.
cause: unknown | undefined; cause: unknown | undefined;
@ -78,16 +78,15 @@ export interface ErrorOptions {
export class UserFriendlyError extends Error { export class UserFriendlyError extends Error {
public readonly translatedMessage: string; public readonly translatedMessage: string;
public constructor(message: TranslationKey, substitutionVariablesAndCause?: IVariables & ErrorOptions) { public constructor(
const errorOptions = { message: TranslationKey,
cause: substitutionVariablesAndCause?.cause, substitutionVariablesAndCause?: Omit<IVariables, keyof ErrorOptions> | ErrorOptions,
}; ) {
// Prevent "Could not find /%\(cause\)s/g in x" logs to the console by removing it from the list // Prevent "Could not find /%\(cause\)s/g in x" logs to the console by removing it from the list
const substitutionVariables = { ...substitutionVariablesAndCause }; const { cause, ...substitutionVariables } = substitutionVariablesAndCause ?? {};
delete substitutionVariables["cause"]; const errorOptions = { cause };
// Create the error with the English version of the message that we want to show // Create the error with the English version of the message that we want to show up in the logs
// up in the logs
const englishTranslatedMessage = _t(message, { ...substitutionVariables, locale: "en" }); const englishTranslatedMessage = _t(message, { ...substitutionVariables, locale: "en" });
super(englishTranslatedMessage, errorOptions); super(englishTranslatedMessage, errorOptions);