From 090586439fe8d8cba6ef26a51d15c1c0c1c25d9b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Aug 2024 08:59:27 +0100 Subject: [PATCH] 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> --- .../structures/GenericDropdownMenu.tsx | 12 ++++++---- src/components/structures/ThreadPanel.tsx | 2 +- .../structures/auth/Registration.tsx | 23 +++++++++++-------- src/components/views/avatars/BaseAvatar.tsx | 4 ++-- .../views/dialogs/spotlight/Option.tsx | 1 + .../views/directory/NetworkDropdown.tsx | 8 +++++-- .../views/elements/PersistentApp.tsx | 3 ++- .../views/messages/RoomPredecessorTile.tsx | 10 ++++---- .../views/messages/TextualEvent.tsx | 1 + .../views/messages/TimelineSeparator.tsx | 3 ++- src/components/views/pips/WidgetPip.tsx | 5 ++-- src/components/views/right_panel/UserInfo.tsx | 3 ++- src/components/views/rooms/RoomHeader.tsx | 5 +++- .../views/settings/UserProfileSettings.tsx | 4 ++-- .../views/spaces/QuickSettingsButton.tsx | 2 +- src/hooks/useAccountData.ts | 4 ++-- src/languageHandler.tsx | 17 +++++++------- 17 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/components/structures/GenericDropdownMenu.tsx b/src/components/structures/GenericDropdownMenu.tsx index e0fd3b7f9b..06854768db 100644 --- a/src/components/structures/GenericDropdownMenu.tsx +++ b/src/components/structures/GenericDropdownMenu.tsx @@ -97,6 +97,12 @@ type WithKeyFunction = T extends Key toKey: (key: T) => Key; }; +export interface AdditionalOptionsProps { + menuDisplayed: boolean; + closeMenu: () => void; + openMenu: () => void; +} + type IProps = WithKeyFunction & { value: T; options: readonly GenericDropdownMenuOption[] | readonly GenericDropdownMenuGroup[]; @@ -105,11 +111,7 @@ type IProps = WithKeyFunction & { onOpen?: (ev: ButtonEvent) => void; onClose?: (ev: ButtonEvent) => void; className?: string; - AdditionalOptions?: FunctionComponent<{ - menuDisplayed: boolean; - closeMenu: () => void; - openMenu: () => void; - }>; + AdditionalOptions?: FunctionComponent; }; export function GenericDropdownMenu({ diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 8951cfcb91..792c97bc52 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -117,7 +117,7 @@ export const ThreadPanelHeader: React.FC<{ ) : null; const onMarkAllThreadsReadClick = React.useCallback( - (e) => { + (e: React.MouseEvent) => { PosthogTrackers.trackInteraction("WebThreadsMarkAllReadButton", e); if (!roomContext.room) { logger.error("No room in context to mark all threads read"); diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 5ac49537c5..ede5767745 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -248,15 +248,20 @@ export default class Registration extends React.Component { logger.error("Failed to get login flows to check for SSO support", e); } - this.setState(({ flows }) => ({ - matrixClient: cli, - ssoFlow, - oidcNativeFlow, - // if we are using oidc native we won't continue with flow discovery on HS - // so set an empty array to indicate flows are no longer loading - flows: oidcNativeFlow ? [] : flows, - busy: false, - })); + await new Promise((resolve) => { + this.setState( + ({ flows }) => ({ + matrixClient: cli, + ssoFlow, + oidcNativeFlow, + // if we are using oidc native we won't continue with flow discovery on HS + // 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 // since we are going to use OIDC native flow diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index d956c26da2..b35109f5d7 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -19,7 +19,7 @@ limitations under the License. import React, { forwardRef, useCallback, useContext, useEffect, useState } from "react"; 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 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 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. // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP. const reconnected = syncState !== "ERROR" && prevState !== syncState; diff --git a/src/components/views/dialogs/spotlight/Option.tsx b/src/components/views/dialogs/spotlight/Option.tsx index c7d504aa0b..d15b781fcf 100644 --- a/src/components/views/dialogs/spotlight/Option.tsx +++ b/src/components/views/dialogs/spotlight/Option.tsx @@ -26,6 +26,7 @@ interface OptionProps { id?: string; className?: string; onClick: ((ev: ButtonEvent) => void) | null; + children?: ReactNode; } export const Option: React.FC = ({ inputRef, children, endAdornment, className, ...props }) => { diff --git a/src/components/views/directory/NetworkDropdown.tsx b/src/components/views/directory/NetworkDropdown.tsx index 8736e974e8..deb9285853 100644 --- a/src/components/views/directory/NetworkDropdown.tsx +++ b/src/components/views/directory/NetworkDropdown.tsx @@ -26,7 +26,11 @@ import SdkConfig from "../../../SdkConfig"; import { SettingLevel } from "../../../settings/SettingLevel"; import SettingsStore from "../../../settings/SettingsStore"; import { Protocols } from "../../../utils/DirectoryUtils"; -import { GenericDropdownMenu, GenericDropdownMenuItem } from "../../structures/GenericDropdownMenu"; +import { + AdditionalOptionsProps, + GenericDropdownMenu, + GenericDropdownMenuItem, +} from "../../structures/GenericDropdownMenu"; import TextInputDialog from "../dialogs/TextInputDialog"; import AccessibleButton from "../elements/AccessibleButton"; import withValidation from "../elements/Validation"; @@ -181,7 +185,7 @@ export const NetworkDropdown: React.FC = ({ protocols, config, setConfig })); const addNewServer = useCallback( - ({ closeMenu }) => ( + ({ closeMenu }: AdditionalOptionsProps) => ( <> void) | undefined>; + children?: ReactNode; } export default class PersistentApp extends React.Component { diff --git a/src/components/views/messages/RoomPredecessorTile.tsx b/src/components/views/messages/RoomPredecessorTile.tsx index 3166373fe0..36679d906d 100644 --- a/src/components/views/messages/RoomPredecessorTile.tsx +++ b/src/components/views/messages/RoomPredecessorTile.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { useCallback, useContext } from "react"; 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 { Action } from "../../../dispatcher/actions"; @@ -52,7 +52,7 @@ export const RoomPredecessorTile: React.FC = ({ mxEvent, timestamp }) => const predecessor = useRoomState( roomContext.room, useCallback( - (state) => state.findPredecessor(msc3946ProcessDynamicPredecessor), + (state: RoomState) => state.findPredecessor(msc3946ProcessDynamicPredecessor), [msc3946ProcessDynamicPredecessor], ), ); @@ -63,9 +63,9 @@ export const RoomPredecessorTile: React.FC = ({ mxEvent, timestamp }) => dis.dispatch({ action: Action.ViewRoom, - event_id: predecessor.eventId, + event_id: predecessor?.eventId, highlighted: true, - room_id: predecessor.roomId, + room_id: predecessor?.roomId, metricsTrigger: "Predecessor", metricsViaKeyboard: e.type !== "click", }); @@ -126,7 +126,7 @@ export const RoomPredecessorTile: React.FC = ({ mxEvent, timestamp }) => const predecessorPermalink = prevRoom ? createLinkWithRoom(prevRoom, predecessor.roomId, predecessor.eventId) - : createLinkWithoutRoom(predecessor.roomId, predecessor.viaServers, predecessor.eventId); + : createLinkWithoutRoom(predecessor.roomId, predecessor?.viaServers ?? [], predecessor.eventId); const link = ( diff --git a/src/components/views/messages/TextualEvent.tsx b/src/components/views/messages/TextualEvent.tsx index ae94fd31f9..35351ce531 100644 --- a/src/components/views/messages/TextualEvent.tsx +++ b/src/components/views/messages/TextualEvent.tsx @@ -27,6 +27,7 @@ interface IProps { export default class TextualEvent extends React.Component { public static contextType = RoomContext; + public declare context: React.ContextType; public render(): React.ReactNode { const text = TextForEvent.textForEvent( diff --git a/src/components/views/messages/TimelineSeparator.tsx b/src/components/views/messages/TimelineSeparator.tsx index 78e0d1fd65..b3a2b9ccfb 100644 --- a/src/components/views/messages/TimelineSeparator.tsx +++ b/src/components/views/messages/TimelineSeparator.tsx @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; interface Props { label: string; + children?: ReactNode; } export const enum SeparatorKind { diff --git a/src/components/views/pips/WidgetPip.tsx b/src/components/views/pips/WidgetPip.tsx index 9bba2ccc53..a1710d16cc 100644 --- a/src/components/views/pips/WidgetPip.tsx +++ b/src/components/views/pips/WidgetPip.tsx @@ -34,6 +34,7 @@ import { WidgetType } from "../../../widgets/WidgetType"; import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; import WidgetUtils from "../../../utils/WidgetUtils"; import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions"; +import { ButtonEvent } from "../elements/AccessibleButton"; interface Props { widgetId: string; @@ -62,7 +63,7 @@ export const WidgetPip: FC = ({ widgetId, room, viewingRoom, onStartMovin const call = useCallForWidget(widgetId, room.roomId); const onBackClick = useCallback( - (ev) => { + (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -87,7 +88,7 @@ export const WidgetPip: FC = ({ widgetId, room, viewingRoom, onStartMovin ); const onLeaveClick = useCallback( - (ev) => { + (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 6f8fd9790b..159bc9dbf1 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -424,6 +424,7 @@ export const UserOptionsSection: React.FC<{ member: Member; canInvite: boolean; isSpace?: boolean; + children?: ReactNode; }> = ({ member, canInvite, isSpace, children }) => { const cli = useContext(MatrixClientContext); @@ -1036,7 +1037,7 @@ const IgnoreToggleButton: React.FC<{ }, [cli, member.userId]); // Recheck also if we receive new accountData m.ignored_user_list const accountDataHandler = useCallback( - (ev) => { + (ev: MatrixEvent) => { if (ev.getType() === "m.ignored_user_list") { setIsIgnored(cli.isUserIgnored(member.userId)); } diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index 9cf63e474d..11873ee129 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -116,7 +116,10 @@ export default function RoomHeader({ 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 = ( diff --git a/src/components/views/settings/UserProfileSettings.tsx b/src/components/views/settings/UserProfileSettings.tsx index a104aabb1d..b793f27dd6 100644 --- a/src/components/views/settings/UserProfileSettings.tsx +++ b/src/components/views/settings/UserProfileSettings.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and 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 { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web"; 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 { Flex } from "../../utils/Flex"; -const SpinnerToast: React.FC = ({ children }) => ( +const SpinnerToast: React.FC<{ children?: ReactNode }> = ({ children }) => ( <> {children} diff --git a/src/components/views/spaces/QuickSettingsButton.tsx b/src/components/views/spaces/QuickSettingsButton.tsx index cf031e4ff1..a118a13bbc 100644 --- a/src/components/views/spaces/QuickSettingsButton.tsx +++ b/src/components/views/spaces/QuickSettingsButton.tsx @@ -45,7 +45,7 @@ const QuickSettingsButton: React.FC<{ useSettingValue>("Spaces.enabledMetaSpaces"); const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); - const developerModeEnabled = useSettingValue("developerMode"); + const developerModeEnabled = useSettingValue("developerMode"); let contextMenu: JSX.Element | undefined; if (menuDisplayed && handle.current) { diff --git a/src/hooks/useAccountData.ts b/src/hooks/useAccountData.ts index d59910d503..ad25f61465 100644 --- a/src/hooks/useAccountData.ts +++ b/src/hooks/useAccountData.ts @@ -26,9 +26,9 @@ export const useAccountData = (cli: MatrixClient, eventType: strin const [value, setValue] = useState(() => tryGetContent(cli.getAccountData(eventType))); const handler = useCallback( - (event) => { + (event: MatrixEvent) => { if (event.getType() !== eventType) return; - setValue(event.getContent()); + setValue(event.getContent()); }, [eventType], ); diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 0a80be89f4..27a886ddf9 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -53,7 +53,7 @@ const FALLBACK_LOCALE = "en"; counterpart.setFallbackLocale(FALLBACK_LOCALE); 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 // or not to avoid typos of "cause" slipping through unnoticed. cause: unknown | undefined; @@ -78,16 +78,15 @@ export interface ErrorOptions { export class UserFriendlyError extends Error { public readonly translatedMessage: string; - public constructor(message: TranslationKey, substitutionVariablesAndCause?: IVariables & ErrorOptions) { - const errorOptions = { - cause: substitutionVariablesAndCause?.cause, - }; + public constructor( + message: TranslationKey, + substitutionVariablesAndCause?: Omit | ErrorOptions, + ) { // Prevent "Could not find /%\(cause\)s/g in x" logs to the console by removing it from the list - const substitutionVariables = { ...substitutionVariablesAndCause }; - delete substitutionVariables["cause"]; + const { cause, ...substitutionVariables } = substitutionVariablesAndCause ?? {}; + const errorOptions = { cause }; - // Create the error with the English version of the message that we want to show - // up in the logs + // Create the error with the English version of the message that we want to show up in the logs const englishTranslatedMessage = _t(message, { ...substitutionVariables, locale: "en" }); super(englishTranslatedMessage, errorOptions);