From b5f4a078682e2c3d48c8cf64a13738bd1a870825 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 6 Jan 2025 18:00:20 +0100 Subject: [PATCH 1/2] Upgrade to React Router v6 (#2919) --- package.json | 5 +- src/App.tsx | 43 ++++------- src/ClientContext.tsx | 8 +- src/UserMenu.tsx | 2 +- src/UserMenuContainer.tsx | 8 +- src/auth/LoginPage.tsx | 10 +-- src/auth/RegisterPage.tsx | 14 ++-- src/button/Link.tsx | 29 ++----- src/button/LinkButton.tsx | 8 +- src/home/RegisteredView.tsx | 12 +-- src/home/UnauthenticatedView.tsx | 10 +-- src/initializer.tsx | 15 +++- src/main.tsx | 5 +- src/room/CallEndedView.tsx | 8 +- src/room/GroupCallView.test.tsx | 8 +- src/room/GroupCallView.tsx | 8 +- src/room/LobbyView.tsx | 8 +- src/useLocationNavigation.ts | 36 --------- yarn.lock | 129 ++++++------------------------- 19 files changed, 112 insertions(+), 254 deletions(-) delete mode 100644 src/useLocationNavigation.ts diff --git a/package.json b/package.json index bc85b9c6..7c7e6885 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,8 @@ "@types/node": "^22.0.0", "@types/pako": "^2.0.3", "@types/qrcode": "^1.5.5", + "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", - "@types/react-router-dom": "^5.3.3", "@types/sdp-transform": "^2.4.5", "@types/uuid": "10", "@typescript-eslint/eslint-plugin": "^8.0.0", @@ -82,7 +82,6 @@ "eslint-plugin-rxjs": "^5.0.3", "eslint-plugin-unicorn": "^56.0.0", "global-jsdom": "^25.0.0", - "history": "^4.0.0", "i18next": "^23.0.0", "i18next-browser-languagedetector": "^8.0.0", "i18next-parser": "^9.0.0", @@ -104,7 +103,7 @@ "react": "18", "react-dom": "18", "react-i18next": "^15.0.0", - "react-router-dom": "^5.2.0", + "react-router-dom": "^6.28.0", "react-use-clipboard": "^1.0.7", "react-use-measure": "^2.1.1", "rxjs": "^7.8.1", diff --git a/src/App.tsx b/src/App.tsx index 288d4c9d..62dc5c8f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,14 +6,8 @@ Please see LICENSE in the repository root for full details. */ import { type FC, Suspense, useEffect, useState } from "react"; -import { - BrowserRouter as Router, - Switch, - Route, - useLocation, -} from "react-router-dom"; +import { BrowserRouter, Route, useLocation, Routes } from "react-router-dom"; import * as Sentry from "@sentry/react"; -import { type History } from "history"; import { TooltipProvider } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; @@ -29,7 +23,7 @@ import { MediaDevicesProvider } from "./livekit/MediaDevicesContext"; import { widget } from "./widget"; import { useTheme } from "./useTheme"; -const SentryRoute = Sentry.withSentryRouting(Route); +const SentryRoute = Sentry.withSentryReactRouterV6Routing(Route); interface SimpleProviderProps { children: JSX.Element; @@ -55,11 +49,7 @@ const ThemeProvider: FC = ({ children }) => { return children; }; -interface AppProps { - history: History; -} - -export const App: FC = ({ history }) => { +export const App: FC = () => { const [loaded, setLoaded] = useState(false); useEffect(() => { Initializer.init() @@ -76,7 +66,7 @@ export const App: FC = ({ history }) => { return ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - + @@ -86,20 +76,15 @@ export const App: FC = ({ history }) => { - - - - - - - - - - - - - - + + } /> + } /> + } + /> + } /> + @@ -110,6 +95,6 @@ export const App: FC = ({ history }) => { - + ); }; diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 400784b5..e6eed7ab 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -15,7 +15,7 @@ import { useRef, useMemo, } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { logger } from "matrix-js-sdk/src/logger"; import { useTranslation } from "react-i18next"; import { type ISyncStateData, type SyncState } from "matrix-js-sdk/src/sync"; @@ -144,7 +144,7 @@ interface Props { } export const ClientProvider: FC = ({ children }) => { - const history = useHistory(); + const navigate = useNavigate(); // null = signed out, undefined = loading const [initClientState, setInitClientState] = useState< @@ -228,9 +228,9 @@ export const ClientProvider: FC = ({ children }) => { await client.clearStores(); clearSession(); setInitClientState(null); - history.push("/"); + navigate("/"); PosthogAnalytics.instance.setRegistrationType(RegistrationType.Guest); - }, [history, initClientState?.client]); + }, [navigate, initClientState?.client]); const { t } = useTranslation(); diff --git a/src/UserMenu.tsx b/src/UserMenu.tsx index 0cb9868b..77dd7474 100644 --- a/src/UserMenu.tsx +++ b/src/UserMenu.tsx @@ -82,7 +82,7 @@ export const UserMenu: FC = ({ if (!isAuthenticated) { return ( - + {t("log_in")} ); diff --git a/src/UserMenuContainer.tsx b/src/UserMenuContainer.tsx index 2695552d..e1cc68a2 100644 --- a/src/UserMenuContainer.tsx +++ b/src/UserMenuContainer.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { type FC, useCallback, useState } from "react"; -import { useHistory, useLocation } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import { useClientLegacy } from "./ClientContext"; import { useProfile } from "./profile/useProfile"; @@ -19,7 +19,7 @@ interface Props { export const UserMenuContainer: FC = ({ preventNavigation = false }) => { const location = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const { client, logout, authenticated, passwordlessUser } = useClientLegacy(); const { displayName, avatarUrl } = useProfile(client); const [settingsModalOpen, setSettingsModalOpen] = useState(false); @@ -45,11 +45,11 @@ export const UserMenuContainer: FC = ({ preventNavigation = false }) => { logout?.(); break; case "login": - history.push("/login", { state: { from: location } }); + navigate("/login", { state: { from: location } }); break; } }, - [history, location, logout, setSettingsModalOpen], + [navigate, location, logout, setSettingsModalOpen], ); const userName = client?.getUserIdLocalpart() ?? ""; diff --git a/src/auth/LoginPage.tsx b/src/auth/LoginPage.tsx index 11b831cd..b7b84530 100644 --- a/src/auth/LoginPage.tsx +++ b/src/auth/LoginPage.tsx @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { type FC, type FormEvent, useCallback, useRef, useState } from "react"; -import { useHistory, useLocation } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; import { Button } from "@vector-im/compound-web"; @@ -29,7 +29,7 @@ export const LoginPage: FC = () => { const homeserver = Config.defaultHomeserverUrl(); // TODO: Make this configurable const usernameRef = useRef(null); const passwordRef = useRef(null); - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); @@ -61,9 +61,9 @@ export const LoginPage: FC = () => { if (locationState && locationState.from) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - history.push(locationState.from); + navigate(locationState.from); } else { - history.push("/"); + navigate("/"); } PosthogAnalytics.instance.eventLogin.track(); }) @@ -72,7 +72,7 @@ export const LoginPage: FC = () => { setLoading(false); }); }, - [login, location, history, homeserver, setClient], + [login, location, navigate, homeserver, setClient], ); // we need to limit the length of the homserver name to not cover the whole loginview input with the string. let shortendHomeserverName = Config.defaultServerName()?.slice(0, 25); diff --git a/src/auth/RegisterPage.tsx b/src/auth/RegisterPage.tsx index b12ff2ca..988d71fc 100644 --- a/src/auth/RegisterPage.tsx +++ b/src/auth/RegisterPage.tsx @@ -14,7 +14,7 @@ import { useRef, useState, } from "react"; -import { useHistory, useLocation } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import { captureException } from "@sentry/react"; import { sleep } from "matrix-js-sdk/src/utils"; import { Trans, useTranslation } from "react-i18next"; @@ -41,7 +41,7 @@ export const RegisterPage: FC = () => { useClientLegacy(); const confirmPasswordRef = useRef(null); - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const [registering, setRegistering] = useState(false); const [error, setError] = useState(); @@ -106,9 +106,9 @@ export const RegisterPage: FC = () => { if (location.state?.from) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - history.push(location.state?.from); + navigate(location.state?.from); } else { - history.push("/"); + navigate("/"); } }) .catch((error) => { @@ -120,7 +120,7 @@ export const RegisterPage: FC = () => { [ register, location, - history, + navigate, passwordlessUser, reset, execute, @@ -141,9 +141,9 @@ export const RegisterPage: FC = () => { useEffect(() => { if (!loading && authenticated && !passwordlessUser && !registering) { - history.push("/"); + navigate("/"); } - }, [loading, history, authenticated, passwordlessUser, registering]); + }, [loading, navigate, authenticated, passwordlessUser, registering]); if (loading) { return ; diff --git a/src/button/Link.tsx b/src/button/Link.tsx index 829cbdc8..65fa3b2e 100644 --- a/src/button/Link.tsx +++ b/src/button/Link.tsx @@ -9,42 +9,27 @@ import { type ComponentPropsWithoutRef, forwardRef, type MouseEvent, - useCallback, - useMemo, } from "react"; import { Link as CpdLink } from "@vector-im/compound-web"; -import { useHistory } from "react-router-dom"; -import { createPath, type LocationDescriptor, type Path } from "history"; +import { type LinkProps, useHref, useLinkClickHandler } from "react-router-dom"; import classNames from "classnames"; -import { useLatest } from "../useLatest"; import styles from "./Link.module.css"; export function useLink( - to: LocationDescriptor, + to: LinkProps["to"], state?: unknown, -): [Path, (e: MouseEvent) => void] { - const latestState = useLatest(state); - const history = useHistory(); - const path = useMemo( - () => (typeof to === "string" ? to : createPath(to)), - [to], - ); - const onClick = useCallback( - (e: MouseEvent) => { - e.preventDefault(); - history.push(to, latestState.current); - }, - [history, to, latestState], - ); +): [string, (e: MouseEvent) => void] { + const href = useHref(to); + const onClick = useLinkClickHandler(to, { state }); - return [path, onClick]; + return [href, onClick]; } type Props = Omit< ComponentPropsWithoutRef, "href" | "onClick" -> & { to: LocationDescriptor; state?: unknown }; +> & { to: LinkProps["to"]; state?: unknown }; /** * A version of Compound's link component that integrates with our router setup. diff --git a/src/button/LinkButton.tsx b/src/button/LinkButton.tsx index b0e733b0..6745903a 100644 --- a/src/button/LinkButton.tsx +++ b/src/button/LinkButton.tsx @@ -7,22 +7,22 @@ Please see LICENSE in the repository root for full details. import { type ComponentPropsWithoutRef, forwardRef } from "react"; import { Button } from "@vector-im/compound-web"; -import { type LocationDescriptor } from "history"; +import type { LinkProps } from "react-router-dom"; import { useLink } from "./Link"; type Props = Omit< ComponentPropsWithoutRef>, "as" | "href" -> & { to: LocationDescriptor }; +> & { to: LinkProps["to"]; state?: unknown }; /** * A version of Compound's button component that acts as a link and integrates * with our router setup. */ export const LinkButton = forwardRef( - function LinkButton({ to, ...props }, ref) { - const [path, onClick] = useLink(to); + function LinkButton({ to, state, ...props }, ref) { + const [path, onClick] = useLink(to, state); return