diff --git a/.syncpackrc.ts b/.syncpackrc.ts index c80893e549cd..f229694a3cc0 100644 --- a/.syncpackrc.ts +++ b/.syncpackrc.ts @@ -37,13 +37,7 @@ export default { '@docusaurus/theme-common', '@docusaurus/types', ], - dependencies: [ - 'react', - 'react-dom', - '@types/react', - '@types/react-router-config', - '@types/react-router-dom', - ], + dependencies: ['react', 'react-dom', '@types/react'], isIgnored: true, }, diff --git a/packages/docusaurus-module-type-aliases/package.json b/packages/docusaurus-module-type-aliases/package.json index 0949776dde5b..08000eb517b7 100644 --- a/packages/docusaurus-module-type-aliases/package.json +++ b/packages/docusaurus-module-type-aliases/package.json @@ -15,8 +15,7 @@ "@docusaurus/types": "3.10.1", "@types/history": "^4.7.11", "@types/react": "19.2.14", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", + "react-router": "^8.0.0", "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index 4ab1e21a54e4..bfe66fc53c20 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -41,10 +41,10 @@ declare module '@generated/registry' { } declare module '@generated/routes' { - import type {RouteConfig as RRRouteConfig} from 'react-router-config'; + import type {RouteObject} from 'react-router'; import type Loadable from 'react-loadable'; - type RouteConfig = RRRouteConfig & { + type RouteConfig = RouteObject & { path: string; component: ReturnType; }; @@ -169,7 +169,7 @@ declare module '@docusaurus/Head' { declare module '@docusaurus/Link' { import type {CSSProperties, ComponentProps, ReactNode} from 'react'; - import type {NavLinkProps as RRNavLinkProps} from 'react-router-dom'; + import type {NavLinkProps as RRNavLinkProps} from 'react-router'; type NavLinkProps = Partial; export type Props = NavLinkProps & @@ -180,6 +180,10 @@ declare module '@docusaurus/Link' { readonly to?: string; readonly href?: string; readonly autoAddBaseUrl?: boolean; + // TODO double check these, + // comes from https://github.com/facebook/docusaurus/pull/6037 + readonly isActive?: boolean; + readonly activeClassName?: string; /** Escape hatch in case broken links check doesn't make sense. */ readonly 'data-noBrokenLinkCheck'?: boolean; @@ -260,7 +264,9 @@ declare module '@docusaurus/Translate' { } declare module '@docusaurus/router' { - export {useHistory, useLocation, Redirect, matchPath} from 'react-router-dom'; + // TODO expose everything? + // remove abstraction? + export {useNavigate, useLocation, Redirect, matchPath} from 'react-router'; } declare module '@docusaurus/useIsomorphicLayoutEffect' { @@ -351,9 +357,9 @@ declare module '@docusaurus/Noop' { } declare module '@docusaurus/renderRoutes' { - import {renderRoutes} from 'react-router-config'; + import {useRoutes} from 'react-router'; - export default renderRoutes; + export default useRoutes; } declare module '@docusaurus/useGlobalData' { diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json index 45f84b567cbc..7e3e19b08dc6 100644 --- a/packages/docusaurus-plugin-content-docs/package.json +++ b/packages/docusaurus-plugin-content-docs/package.json @@ -44,7 +44,6 @@ "@docusaurus/utils": "3.10.1", "@docusaurus/utils-common": "3.10.1", "@docusaurus/utils-validation": "3.10.1", - "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.2.0", "js-yaml": "^4.1.0", diff --git a/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx b/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx index 498e3924b54b..1e6d11060c13 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx +++ b/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx @@ -8,7 +8,7 @@ import {describe, expect, it} from 'vitest'; import React from 'react'; import {renderHook} from '@testing-library/react'; -import {StaticRouter} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import {Context} from '@docusaurus/core/src/client/docusaurusContext'; import { findFirstSidebarItemLink, diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts b/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts index 29bb9c0e0622..1c156ed7853d 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts @@ -32,11 +32,15 @@ export function getActivePlugin( .sort((a, b) => b[1].path.localeCompare(a[1].path)) .find( ([, pluginData]) => - !!matchPath(pathname, { - path: pluginData.path, - exact: false, - strict: false, - }), + !!matchPath( + pathname, + // @ts-expect-error: TODO review + { + path: pluginData.path, + exact: false, + strict: false, + }, + ), ); const activePlugin: ActivePlugin | undefined = activeEntry @@ -81,11 +85,15 @@ export function getActiveVersion( return sortedVersions.find( (version) => - !!matchPath(pathname, { - path: version.path, - exact: false, - strict: false, - }), + !!matchPath( + pathname, + // @ts-expect-error: TODO review + { + path: version.path, + exact: false, + strict: false, + }, + ), ); } @@ -96,11 +104,16 @@ export function getActiveDocContext( const activeVersion = getActiveVersion(data, pathname); const activeDoc = activeVersion?.docs.find( (doc) => - !!matchPath(pathname, { - path: doc.path, - exact: true, - strict: false, - }), + !!matchPath( + pathname, + // @ts-expect-error: TODO review + + { + path: doc.path, + exact: true, + strict: false, + }, + ), ); function getAlternateVersionDocs( diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx index 474a58895ade..43936477a118 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx +++ b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx @@ -6,6 +6,7 @@ */ import {type ReactNode, useMemo} from 'react'; +import {type RouteObject} from 'react-router'; import {matchPath, useLocation} from '@docusaurus/router'; import renderRoutes from '@docusaurus/renderRoutes'; import { @@ -401,15 +402,19 @@ export function useDocRootMetadata({route}: DocRootProps): null | { } { const location = useLocation(); const versionMetadata = useDocsVersion(); - const docRoutes = route.routes!; + + // TODO review + const docRoutes = (route as any).routes as RouteObject[]; const currentDocRoute = docRoutes.find((docRoute) => - matchPath(location.pathname, docRoute), + matchPath(docRoute.path!, location.pathname), ); + if (!currentDocRoute) { return null; } // For now, the sidebarName is added as route config: not ideal! + // @ts-expect-error: TODO review const sidebarName = currentDocRoute.sidebar as string; const sidebarItems = sidebarName diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index f89b3f63169e..6d979370f4a9 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -635,10 +635,11 @@ declare module '@theme/DocBreadcrumbs' { declare module '@theme/DocsRoot' { import type {ReactNode} from 'react'; - import type {RouteConfigComponentProps} from 'react-router-config'; import type {Required} from 'utility-types'; - export interface Props extends Required {} + type RouteProps = {}; // TODO + + export interface Props extends Required {} export default function DocsRoot(props: Props): ReactNode; } @@ -646,10 +647,11 @@ declare module '@theme/DocsRoot' { declare module '@theme/DocVersionRoot' { import type {ReactNode} from 'react'; import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs'; - import type {RouteConfigComponentProps} from 'react-router-config'; import type {Required} from 'utility-types'; - export interface Props extends Required { + type RouteProps = {}; // TODO + + export interface Props extends Required { readonly version: PropVersionMetadata; } @@ -658,10 +660,11 @@ declare module '@theme/DocVersionRoot' { declare module '@theme/DocRoot' { import type {ReactNode} from 'react'; - import type {RouteConfigComponentProps} from 'react-router-config'; import type {Required} from 'utility-types'; - export interface Props extends Required {} + type RouteProps = {}; // TODO + + export interface Props extends Required {} export default function DocRoot(props: Props): ReactNode; } diff --git a/packages/docusaurus-plugin-content-docs/src/routes.ts b/packages/docusaurus-plugin-content-docs/src/routes.ts index cde7d8831241..f89233c0edce 100644 --- a/packages/docusaurus-plugin-content-docs/src/routes.ts +++ b/packages/docusaurus-plugin-content-docs/src/routes.ts @@ -14,6 +14,7 @@ import { groupTaggedItems, getTagVisibility, } from '@docusaurus/utils'; +import {addTrailingSlash} from '@docusaurus/utils-common'; import { toTagDocListProp, toTagsListTagsProp, @@ -107,7 +108,7 @@ async function buildVersionSidebarRoute(param: BuildVersionRoutesParam) { ]); const subRoutes = [...docRoutes, ...categoryGeneratedIndexRoutes]; return { - path: param.version.path, + path: addTrailingSlash(param.version.path), exact: false, component: param.options.docRootComponent, routes: subRoutes, @@ -196,7 +197,7 @@ async function buildVersionRoutes( async function doBuildVersionRoutes(): Promise { return { - path: version.path, + path: addTrailingSlash(version.path), exact: false, component: options.docVersionRootComponent, routes: await buildVersionSubRoutes(), diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index b55b06573f33..ea8eced35499 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -42,7 +42,6 @@ "postcss": "^8.5.12", "prism-react-renderer": "^2.4.1", "prismjs": "^1.29.0", - "react-router-dom": "^5.3.4", "rtlcss": "^4.1.0", "tslib": "^2.6.0", "utility-types": "^3.10.0" diff --git a/packages/docusaurus-theme-classic/src/theme/DocRoot/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocRoot/index.tsx index ab5fef58c450..a43ecb765d81 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocRoot/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocRoot/index.tsx @@ -7,6 +7,7 @@ import React, {type ReactNode} from 'react'; import clsx from 'clsx'; +import {useMatches} from 'react-router'; import {HtmlClassNameProvider, ThemeClassNames} from '@docusaurus/theme-common'; import { DocsSidebarProvider, @@ -17,6 +18,9 @@ import NotFoundContent from '@theme/NotFound/Content'; import type {Props} from '@theme/DocRoot'; export default function DocRoot(props: Props): ReactNode { + const matches = useMatches(); + console.log('DocRoot props', {props, matches}); + const currentDocRouteMetadata = useDocRootMetadata(props); if (!currentDocRouteMetadata) { // We only render the not found content to avoid a double layout diff --git a/packages/docusaurus-theme-classic/src/theme/DocVersionRoot/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocVersionRoot/index.tsx index 7e13cce3cb9f..9cc60834c4f6 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocVersionRoot/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocVersionRoot/index.tsx @@ -6,12 +6,12 @@ */ import React, {type ReactNode} from 'react'; +import {Outlet} from 'react-router'; import {HtmlClassNameProvider, PageMetadata} from '@docusaurus/theme-common'; import { getDocsVersionSearchTag, DocsVersionProvider, } from '@docusaurus/plugin-content-docs/client'; -import renderRoutes from '@docusaurus/renderRoutes'; import SearchMetadata from '@theme/SearchMetadata'; import type {Props} from '@theme/DocVersionRoot'; @@ -32,11 +32,11 @@ function DocVersionRootMetadata(props: Props): ReactNode { } function DocVersionRootContent(props: Props): ReactNode { - const {version, route} = props; + const {version} = props; return ( - {renderRoutes(route.routes!)} + ); diff --git a/packages/docusaurus-theme-classic/src/theme/DocsRoot/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocsRoot/index.tsx index 12f4a435571d..4ca4bcd12c33 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocsRoot/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocsRoot/index.tsx @@ -8,15 +8,17 @@ import React, {type ReactNode} from 'react'; import clsx from 'clsx'; import {ThemeClassNames, HtmlClassNameProvider} from '@docusaurus/theme-common'; -import renderRoutes from '@docusaurus/renderRoutes'; +import {Outlet} from 'react-router'; import Layout from '@theme/Layout'; import type {Props} from '@theme/DocVersionRoot'; -export default function DocsRoot(props: Props): ReactNode { +export default function DocsRoot(_props: Props): ReactNode { return ( - {renderRoutes(props.route.routes!)} + + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index 388f7e912769..00b7651478b4 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -15,7 +15,7 @@ import { } from '@docusaurus/theme-common'; import SkipToContent from '@theme/SkipToContent'; import AnnouncementBar from '@theme/AnnouncementBar'; -import Navbar from '@theme/Navbar'; +// import Navbar from '@theme/Navbar'; import Footer from '@theme/Footer'; import LayoutProvider from '@theme/Layout/Provider'; import ErrorPageContent from '@theme/ErrorPageContent'; @@ -40,7 +40,9 @@ export default function Layout(props: Props): ReactNode { + {/* + */}
pageActive || (!!activeDoc?.sidebar && activeDoc.sidebar === doc.sidebar) diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx index 23943af1da77..0f7ce2c343d0 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx @@ -30,6 +30,7 @@ export default function DocSidebarNavbarItem({ activeDoc?.sidebar === sidebarId} label={label ?? sidebarLink.label} to={sidebarLink.path} diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx index fdd28ebc251f..e396ab3a38d0 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx @@ -138,6 +138,7 @@ export default function DocsVersionDropdownNavbarItem({ label, // preserve ?search#hash suffix on version switches to: `${targetDoc.path}${search}${hash}`, + // @ts-expect-error: TODO review isActive: () => version === activeDocContext.activeVersion, onClick: () => savePreferredVersionName(version.name), }; @@ -176,6 +177,7 @@ export default function DocsVersionDropdownNavbarItem({ mobile={mobile} label={dropdownLabel} to={dropdownTo} + // @ts-expect-error: TODO review isActive={dropdownActiveClassDisabled ? () => false : undefined} /> ); @@ -188,6 +190,7 @@ export default function DocsVersionDropdownNavbarItem({ label={dropdownLabel} to={dropdownTo} items={items} + // @ts-expect-error: TODO review isActive={dropdownActiveClassDisabled ? () => false : undefined} /> ); diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/NavbarNavLink.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/NavbarNavLink.tsx index 52e198d33b81..06aafb7b4675 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/NavbarNavLink.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/NavbarNavLink.tsx @@ -58,10 +58,12 @@ export default function NavbarNavLink({ } return ( + // @ts-expect-error: TODO review activeBaseRegex ? isRegexpStringMatch(activeBaseRegex, location.pathname) diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx index 7a07b714e612..933dc6e6d4a2 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx @@ -11,7 +11,7 @@ import type {PropsWithChildren, ReactNode} from 'react'; import {render} from '@testing-library/react'; import '@testing-library/jest-dom/vitest'; import {ScrollControllerProvider} from '@docusaurus/theme-common/internal'; -import {StaticRouter} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import Tabs from '../index'; import TabItem from '../../TabItem'; diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json index cde98294dc37..d43e866f2587 100644 --- a/packages/docusaurus-theme-common/package.json +++ b/packages/docusaurus-theme-common/package.json @@ -36,7 +36,6 @@ "@docusaurus/utils-common": "3.10.1", "@types/history": "^4.7.11", "@types/react": "19.2.14", - "@types/react-router-config": "*", "clsx": "^2.0.0", "parse-numeric-range": "^1.3.0", "prism-react-renderer": "^2.4.1", diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts index 07d9ed9b9b58..1e6658f9974e 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts @@ -7,7 +7,7 @@ import {describe, expect, it} from 'vitest'; import {findHomePageRoute, isSamePath} from '../routesUtils'; -import type {RouteConfig} from 'react-router-config'; +import type {RouteObject} from 'react-router'; describe('isSamePath', () => { it('returns true for compared path without trailing slash', () => { @@ -42,7 +42,7 @@ describe('isSamePath', () => { }); describe('findHomePageRoute', () => { - const homePage: RouteConfig = { + const homePage: RouteObject = { path: '/', exact: true, }; diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx index b2ea5738cec4..517da5c4838c 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx +++ b/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx @@ -8,7 +8,7 @@ import {describe, expect, it} from 'vitest'; import React from 'react'; import {renderHook} from '@testing-library/react'; -import {StaticRouter} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import {Context} from '@docusaurus/core/src/client/docusaurusContext'; import {fromPartial} from '@total-typescript/shoehorn'; import {useAlternatePageUtils} from '../useAlternatePageUtils'; diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx index 5c9be408f04a..3017b3a2e812 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx +++ b/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx @@ -8,7 +8,7 @@ import {describe, expect, it} from 'vitest'; import React from 'react'; import {renderHook} from '@testing-library/react'; -import {StaticRouter} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import {Context} from '@docusaurus/core/src/client/docusaurusContext'; import {useLocalPathname} from '../useLocalPathname'; import type {DocusaurusContext} from '@docusaurus/types'; diff --git a/packages/docusaurus-theme-common/src/utils/historyUtils.ts b/packages/docusaurus-theme-common/src/utils/historyUtils.ts index 45f9cc22dd01..a2783b509695 100644 --- a/packages/docusaurus-theme-common/src/utils/historyUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/historyUtils.ts @@ -5,11 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -import {useCallback, useEffect, useMemo, useSyncExternalStore} from 'react'; -import {useHistory} from '@docusaurus/router'; +import {useCallback, useMemo, useSyncExternalStore} from 'react'; +import { + useBlocker, + useNavigate, + createBrowserRouter, + type RouterState, + type Location, +} from 'react-router'; import {useEvent} from './reactUtils'; -import type {History, Location, Action} from 'history'; +type Action = RouterState['historyAction']; type HistoryBlockHandler = (location: Location, action: Action) => void | false; @@ -19,13 +25,12 @@ type HistoryBlockHandler = (location: Location, action: Action) => void | false; * will be blocked/cancelled. */ function useHistoryActionHandler(handler: HistoryBlockHandler): void { - const history = useHistory(); const stableHandler = useEvent(handler); - useEffect( - // See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md - () => history.block((location, action) => stableHandler(location, action)), - [history, stableHandler], - ); + + // TODO double-check this works + useBlocker(({nextLocation, historyAction}) => { + return stableHandler(nextLocation, historyAction) ?? false; + }); } /** @@ -51,17 +56,17 @@ export function useHistoryPopHandler(handler: HistoryBlockHandler): void { * @param selector */ export function useHistorySelector( - selector: (history: History) => Value, + selector: (history: {location: Location}) => Value, ): Value { - const history = useHistory(); + // TODO fix this, import real router singleton + const router = createBrowserRouter([]); return useSyncExternalStore( - history.listen, - () => selector(history), + router.subscribe, + () => selector({location: router.state.location}), () => selector({ - ...history, location: { - ...history.location, + ...router.state.location, // On the server/hydration, these attributes should always be empty // Forcing empty state makes this hook safe from hydration errors search: '', @@ -88,21 +93,20 @@ export function useQueryStringValue(key: string | null): string | null { function useQueryStringUpdater( key: string, ): (newValue: string | null, options?: {push: boolean}) => void { - const history = useHistory(); + const search = useHistorySelector(({location}) => location.search); + const navigate = useNavigate(); return useCallback( (newValue, options) => { - const searchParams = new URLSearchParams(history.location.search); + const searchParams = new URLSearchParams(search); if (newValue) { searchParams.set(key, newValue); } else { searchParams.delete(key); } - const updateHistory = options?.push ? history.push : history.replace; - updateHistory({ - search: searchParams.toString(), - }); + + navigate({search: searchParams.toString()}, {replace: !options?.push}); }, - [key, history], + [search, navigate, key], ); } @@ -132,23 +136,23 @@ type ListUpdateFunction = ( ) => void; function useQueryStringListUpdater(key: string): ListUpdateFunction { - const history = useHistory(); + const search = useHistorySelector(({location}) => location.search); + + const navigate = useNavigate(); const setValues: ListUpdateFunction = useCallback( (update, options) => { - const searchParams = new URLSearchParams(history.location.search); + const searchParams = new URLSearchParams(search); const newValues = Array.isArray(update) ? update : update(searchParams.getAll(key)); searchParams.delete(key); newValues.forEach((v) => searchParams.append(key, v)); - const updateHistory = options?.push ? history.push : history.replace; - updateHistory({ - search: searchParams.toString(), - }); + navigate({search: searchParams.toString()}, {replace: !options?.push}); }, - [history, key], + [search, navigate, key], ); + return setValues; } @@ -161,12 +165,10 @@ export function useQueryStringList( } export function useClearQueryString(): () => void { - const history = useHistory(); + const navigate = useNavigate(); return useCallback(() => { - history.replace({ - search: undefined, - }); - }, [history]); + navigate({search: undefined}, {replace: true}); + }, [navigate]); } export function mergeSearchParams( diff --git a/packages/docusaurus-theme-common/src/utils/routesUtils.ts b/packages/docusaurus-theme-common/src/utils/routesUtils.ts index b662ab4bf23d..a40e0ba95cd5 100644 --- a/packages/docusaurus-theme-common/src/utils/routesUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/routesUtils.ts @@ -8,7 +8,7 @@ import {useMemo} from 'react'; import generatedRoutes from '@generated/routes'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import type {RouteConfig} from 'react-router-config'; +import type {RouteObject} from 'react-router'; /** * Compare the 2 paths, case insensitive and ignoring trailing slash @@ -34,18 +34,20 @@ export function findHomePageRoute({ baseUrl, routes: initialRoutes, }: { - routes: RouteConfig[]; + routes: RouteObject[]; baseUrl: string; -}): RouteConfig | undefined { - function isHomePageRoute(route: RouteConfig): boolean { +}): RouteObject | undefined { + function isHomePageRoute(route: RouteObject): boolean { + // @ts-expect-error: TODO review return route.path === baseUrl && route.exact === true; } - function isHomeParentRoute(route: RouteConfig): boolean { + function isHomeParentRoute(route: RouteObject): boolean { + // @ts-expect-error: TODO review return route.path === baseUrl && !route.exact; } - function doFindHomePageRoute(routes: RouteConfig[]): RouteConfig | undefined { + function doFindHomePageRoute(routes: RouteObject[]): RouteObject | undefined { if (routes.length === 0) { return undefined; } @@ -55,6 +57,7 @@ export function findHomePageRoute({ } const indexSubRoutes = routes .filter(isHomeParentRoute) + // @ts-expect-error: TODO review .flatMap((route) => route.routes ?? []); return doFindHomePageRoute(indexSubRoutes); } @@ -66,7 +69,7 @@ export function findHomePageRoute({ * Fetches the route that points to "/". Use this instead of the naive "/", * because the homepage may not exist. */ -export function useHomePageRoute(): RouteConfig | undefined { +export function useHomePageRoute(): RouteObject | undefined { const {baseUrl} = useDocusaurusContext().siteConfig; return useMemo( () => findHomePageRoute({routes: generatedRoutes, baseUrl}), diff --git a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx index fcc4228a6830..4fbfa07c6b86 100644 --- a/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/skipToContentUtils.tsx @@ -11,7 +11,7 @@ import React, { type ComponentProps, type ReactNode, } from 'react'; -import {useHistory} from '@docusaurus/router'; +import {useNavigationType} from 'react-router'; import {translate} from '@docusaurus/Translate'; import {useLocationChange} from './useLocationChange'; @@ -60,7 +60,8 @@ function useSkipToContent(): { onClick: (e: React.MouseEvent) => void; } { const containerRef = useRef(null); - const {action} = useHistory(); + + const action = useNavigationType(); const onClick = useCallback((e: React.MouseEvent) => { e.preventDefault(); diff --git a/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx b/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx index 4cb6351527ea..8ae98607dd0d 100644 --- a/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx @@ -14,10 +14,10 @@ import React, { type ReactNode, type ReactElement, } from 'react'; -import {useHistory} from '@docusaurus/router'; import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect'; import {useQueryStringValue} from '@docusaurus/theme-common/internal'; -import {duplicates, useStorageSlot} from '../index'; +import {useNavigate} from 'react-router'; +import {duplicates, useHistorySelector, useStorageSlot} from '../index'; /** * TabValue is the "config" of a given Tab @@ -188,7 +188,8 @@ function useTabQueryString({ queryString = false, groupId, }: Pick) { - const history = useHistory(); + const search = useHistorySelector(({location}) => location.search); + const navigate = useNavigate(); const key = getQueryStringKey({queryString, groupId}); const value = useQueryStringValue(key); @@ -197,11 +198,11 @@ function useTabQueryString({ if (!key) { return; // no-op } - const searchParams = new URLSearchParams(history.location.search); + const searchParams = new URLSearchParams(search); searchParams.set(key, newValue); - history.replace({...history.location, search: searchParams.toString()}); + navigate({search: searchParams.toString()}, {replace: true}); }, - [key, history], + [search, navigate, key], ); return [value, setValue] as const; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 538d18778c44..7886b1ed826f 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -17,7 +17,7 @@ import {DocSearchButton} from '@docsearch/react/button'; import {useDocSearchKeyboardEvents} from '@docsearch/react/useDocSearchKeyboardEvents'; import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; -import {useHistory} from '@docusaurus/router'; +import {useNavigate} from 'react-router'; import {isRegexpStringMatch} from '@docusaurus/theme-common'; import { useAlgoliaContextualFacetFilters, @@ -83,7 +83,7 @@ function importDocSearchModalIfNeeded() { function useNavigator({ externalUrlRegex, }: Pick) { - const history = useHistory(); + const navigate = useNavigate(); const [navigator] = useState(() => { return { navigate(params) { @@ -92,7 +92,7 @@ function useNavigator({ if (isRegexpStringMatch(externalUrlRegex, params.itemUrl)) { window.location.href = params.itemUrl; } else { - history.push(params.itemUrl); + navigate(params.itemUrl); } }, }; diff --git a/packages/docusaurus-types/src/routing.d.ts b/packages/docusaurus-types/src/routing.d.ts index a5a51f914b2d..7c5ca18a93bd 100644 --- a/packages/docusaurus-types/src/routing.d.ts +++ b/packages/docusaurus-types/src/routing.d.ts @@ -98,11 +98,11 @@ export type RouteConfig = { /** * Nested routes config, useful for "layout routes" having subroutes. */ - routes?: RouteConfig[]; + routes?: RouteConfig[]; // TODO rename to children? /** * React router config option: `exact` routes would not match subroutes. */ - exact?: boolean; + exact?: boolean; // TODO remove? /** * React router config option: `strict` routes are sensitive to the presence * of a trailing slash. diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index 13ff3da23ad0..a19e3f0a1f42 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -63,9 +63,7 @@ "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", "react-loadable-ssr-addon-v5-slorber": "^1.0.3", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", + "react-router": "^8.0.0", "semver": "^7.7.4", "serve-handler": "^6.1.7", "tinypool": "^2.1.0", @@ -83,8 +81,6 @@ "@types/escape-html": "^1.0.4", "@types/history": "^4.7.11", "@types/react-dom": "^19.2.3", - "@types/react-router-dom": "^5.3.3", - "@types/react-router-config": "^5.0.7", "@types/serve-handler": "^6.1.4", "@types/update-notifier": "^6.0.4", "@types/webpack-bundle-analyzer": "^4.7.0", diff --git a/packages/docusaurus/src/client/App.tsx b/packages/docusaurus/src/client/App.tsx index bf65e4b1f999..a08b90366168 100644 --- a/packages/docusaurus/src/client/App.tsx +++ b/packages/docusaurus/src/client/App.tsx @@ -7,17 +7,20 @@ import React, {type ReactNode} from 'react'; import '@generated/client-modules'; - +import { + createBrowserRouter, + createHashRouter, + RouterProvider, + Outlet, +} from 'react-router'; import routes from '@generated/routes'; -import {useLocation} from '@docusaurus/router'; -import renderRoutes from '@docusaurus/renderRoutes'; +import siteConfig from '@generated/docusaurus.config'; + import Root from '@theme/Root'; import ThemeProvider from '@theme/ThemeProvider'; import SiteMetadata from '@theme/SiteMetadata'; -import normalizeLocation from './normalizeLocation'; import {BrowserContextProvider} from './browserContext'; import {DocusaurusContextProvider} from './docusaurusContext'; -import PendingNavigation from './PendingNavigation'; import BaseUrlIssueBanner from './BaseUrlIssueBanner'; import SiteMetadataDefaults from './SiteMetadataDefaults'; @@ -26,8 +29,25 @@ import SiteMetadataDefaults from './SiteMetadataDefaults'; import ErrorBoundary from '@docusaurus/ErrorBoundary'; import HasHydratedDataAttribute from './hasHydratedDataAttribute'; -const routesElement = renderRoutes(routes); +const createRouter = + siteConfig.future.experimental_router === 'hash' + ? createHashRouter + : createBrowserRouter; + +const router = createRouter([ + { + Component: AppRoot, + // children: [{path: '/', Component: () =>

Hello

}], + // children: [routes.at(-2)!], + children: routes, + }, +]); +export default function App(): ReactNode { + return ; +} + +/* function AppNavigation() { const location = useLocation(); const normalizedLocation = normalizeLocation(location); @@ -38,7 +58,9 @@ function AppNavigation() { ); } -export default function App(): ReactNode { + */ + +function AppRoot(): ReactNode { return ( @@ -48,7 +70,7 @@ export default function App(): ReactNode { - + diff --git a/packages/docusaurus/src/client/PendingNavigation.tsx b/packages/docusaurus/src/client/PendingNavigation.tsx index 80855c16fed4..e419bb2043e5 100644 --- a/packages/docusaurus/src/client/PendingNavigation.tsx +++ b/packages/docusaurus/src/client/PendingNavigation.tsx @@ -6,7 +6,7 @@ */ import React, {type ReactNode} from 'react'; -import {Route} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import ClientLifecyclesDispatcher, { dispatchLifecycleAction, } from './ClientLifecyclesDispatcher'; @@ -88,7 +88,7 @@ class PendingNavigation extends React.Component { - children} /> + {children} ); } diff --git a/packages/docusaurus/src/client/clientEntry.tsx b/packages/docusaurus/src/client/clientEntry.tsx index db521bb39f50..e93a6d11f0a0 100644 --- a/packages/docusaurus/src/client/clientEntry.tsx +++ b/packages/docusaurus/src/client/clientEntry.tsx @@ -5,24 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import React, {startTransition, type ReactNode} from 'react'; +import React, {startTransition} from 'react'; import ReactDOM, {type ErrorInfo} from 'react-dom/client'; import {HelmetProvider} from 'react-helmet-async'; -import {BrowserRouter, HashRouter} from 'react-router-dom'; -import siteConfig from '@generated/docusaurus.config'; import ExecutionEnvironment from './exports/ExecutionEnvironment'; import App from './App'; import preload from './preload'; import docusaurus from './docusaurus'; -function Router({children}: {children: ReactNode}): ReactNode { - return siteConfig.future.experimental_router === 'hash' ? ( - {children} - ) : ( - {children} - ); -} - const hydrate = Boolean(process.env.HYDRATE_CLIENT_ENTRY); // Client-side render (e.g: running in browser) to become single-page @@ -33,9 +23,7 @@ if (ExecutionEnvironment.canUseDOM) { const app = ( - - - + ); diff --git a/packages/docusaurus/src/client/docusaurus.ts b/packages/docusaurus/src/client/docusaurus.ts index 24dff371728b..725d6e6367b0 100644 --- a/packages/docusaurus/src/client/docusaurus.ts +++ b/packages/docusaurus/src/client/docusaurus.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {matchRoutes} from 'react-router-config'; +import {matchRoutes} from 'react-router'; import routesChunkNames from '@generated/routesChunkNames'; import routes from '@generated/routes'; import prefetchHelper from './prefetch'; @@ -55,7 +55,7 @@ const prefetch: Docusaurus['prefetch'] = ( fetched.add(routePath); // Find all webpack chunk names needed. - const matches = matchRoutes(routes, routePath); + const matches = matchRoutes(routes, routePath) ?? []; const chunkNamesNeeded = matches.flatMap((match) => getChunkNamesToLoad(match.route.path), diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx index 1fab775967ee..efdce3f368e2 100644 --- a/packages/docusaurus/src/client/exports/Link.tsx +++ b/packages/docusaurus/src/client/exports/Link.tsx @@ -12,7 +12,7 @@ import React, { type ComponentType, type ReactNode, } from 'react'; -import {NavLink, Link as RRLink} from 'react-router-dom'; +import {NavLink, Link as RRLink} from 'react-router'; import {applyTrailingSlash} from '@docusaurus/utils-common'; import useDocusaurusContext from './useDocusaurusContext'; import isInternalUrl from './isInternalUrl'; @@ -194,6 +194,7 @@ function Link({ {...props} onMouseEnter={onInteractionEnter} onTouchStart={onInteractionEnter} + // @ts-expect-error: TODO fix this innerRef={handleRef} to={targetLink} // Avoid "React does not recognize the `activeClassName` prop on a DOM diff --git a/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx index fce2c970b6a8..54d719ad985c 100644 --- a/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx @@ -11,7 +11,7 @@ import React, {type ReactNode} from 'react'; import {render as renderRTL} from '@testing-library/react'; import '@testing-library/jest-dom/vitest'; import {fromPartial} from '@total-typescript/shoehorn'; -import {StaticRouter} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import Link from '../Link'; import {Context} from '../../docusaurusContext'; import type {DocusaurusContext} from '@docusaurus/types'; diff --git a/packages/docusaurus/src/client/exports/renderRoutes.ts b/packages/docusaurus/src/client/exports/renderRoutes.ts index 144ae6efd204..55c802ef553e 100644 --- a/packages/docusaurus/src/client/exports/renderRoutes.ts +++ b/packages/docusaurus/src/client/exports/renderRoutes.ts @@ -5,4 +5,5 @@ * LICENSE file in the root directory of this source tree. */ -export {renderRoutes as default} from 'react-router-config'; +// TODO rename ? remove abstraction? +export {useRoutes as default} from 'react-router'; diff --git a/packages/docusaurus/src/client/exports/router.ts b/packages/docusaurus/src/client/exports/router.ts index 453530b353dd..3b4bdf87dcf4 100644 --- a/packages/docusaurus/src/client/exports/router.ts +++ b/packages/docusaurus/src/client/exports/router.ts @@ -5,4 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -export {useHistory, useLocation, Redirect, matchPath} from 'react-router-dom'; +export {useNavigate, useLocation, matchPath} from 'react-router'; diff --git a/packages/docusaurus/src/client/normalizeLocation.ts b/packages/docusaurus/src/client/normalizeLocation.ts index 74192a451c08..9b598117c31c 100644 --- a/packages/docusaurus/src/client/normalizeLocation.ts +++ b/packages/docusaurus/src/client/normalizeLocation.ts @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {matchRoutes} from 'react-router-config'; -import routes from '@generated/routes'; +// import {matchRoutes} from 'react-router'; +// import routes from '@generated/routes'; import type {Location} from 'history'; // Memoize previously normalized pathnames. @@ -20,13 +20,16 @@ export default function normalizeLocation(location: T): T { }; } + /* + // TODO review this // If the location was registered with an `.html` extension, we don't strip it // away, or it will render to a 404 page. const matchedRoutes = matchRoutes(routes, location.pathname); - if (matchedRoutes.some(({route}) => route.exact === true)) { + if (matchedRoutes?.some(({route}) => route.exact === true)) { pathnames.set(location.pathname, location.pathname); return location; } + */ const pathname = location.pathname.trim().replace(/(?:\/index)?\.html$/, '') || '/'; diff --git a/packages/docusaurus/src/client/preload.ts b/packages/docusaurus/src/client/preload.ts index 08f7c81cad9f..7c5507a8d3d4 100644 --- a/packages/docusaurus/src/client/preload.ts +++ b/packages/docusaurus/src/client/preload.ts @@ -6,7 +6,7 @@ */ import routes from '@generated/routes'; -import {matchRoutes} from 'react-router-config'; +import {matchRoutes} from 'react-router'; /** * Helper function to make sure all async components for that particular route @@ -17,8 +17,13 @@ import {matchRoutes} from 'react-router-config'; * @returns Promise object represents whether pathname has been preloaded */ export default function preload(pathname: string): Promise { + // TODO temporary disabled + if (pathname) { + return Promise.all([]); + } + const matches = Array.from(new Set([pathname, decodeURI(pathname)])).flatMap( - (p) => matchRoutes(routes, p), + (p) => matchRoutes(routes, p) ?? [], ); return Promise.all(matches.map((match) => match.route.component.preload?.())); diff --git a/packages/docusaurus/src/client/serverEntry.tsx b/packages/docusaurus/src/client/serverEntry.tsx index 1ca10a1d3826..aeae9500354a 100644 --- a/packages/docusaurus/src/client/serverEntry.tsx +++ b/packages/docusaurus/src/client/serverEntry.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import {StaticRouter} from 'react-router-dom'; +import {StaticRouter} from 'react-router'; import {HelmetProvider, type FilledContext} from 'react-helmet-async'; import Loadable from 'react-loadable'; import {renderToHtml} from './renderToHtml'; @@ -23,7 +23,6 @@ const render: AppRenderer['render'] = async ({pathname}) => { await preload(pathname); const modules = new Set(); - const routerContext = {}; const helmetContext = {}; const statefulBrokenLinks = createStatefulBrokenLinks(); @@ -31,7 +30,7 @@ const render: AppRenderer['render'] = async ({pathname}) => { // @ts-expect-error: we are migrating away from react-loadable anyways modules.add(moduleName)}> - + diff --git a/packages/docusaurus/src/server/brokenLinks.ts b/packages/docusaurus/src/server/brokenLinks.ts index 3ee1971f8128..88434b1eaf42 100644 --- a/packages/docusaurus/src/server/brokenLinks.ts +++ b/packages/docusaurus/src/server/brokenLinks.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import logger from '@docusaurus/logger'; -import {matchRoutes as reactRouterMatchRoutes} from 'react-router-config'; +import {matchRoutes} from 'react-router'; import { parseURLPath, serializeURLPath, @@ -17,13 +17,6 @@ import { import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils-common'; import type {RouteConfig, ReportingSeverity} from '@docusaurus/types'; -function matchRoutes(routeConfig: RouteConfig[], pathname: string) { - // @ts-expect-error: React router types RouteConfig with an actual React - // component, but we load route components with string paths. - // We don't actually access component here, so it's fine. - return reactRouterMatchRoutes(routeConfig, pathname); -} - type BrokenLink = { link: string; resolvedLink: string; @@ -89,7 +82,7 @@ function createBrokenLinksHelper({ })(); function isPathnameMatchingAnyRoute(pathname: string): boolean { - if (matchRoutes(remainingRoutes, pathname).length > 0) { + if (matchRoutes(remainingRoutes, pathname)?.length ?? 0 > 0) { // IMPORTANT: this is an optimization // See https://github.com/facebook/docusaurus/issues/9754 // Large Docusaurus sites have many routes! diff --git a/packages/docusaurus/src/server/codegen/codegenRoutes.ts b/packages/docusaurus/src/server/codegen/codegenRoutes.ts index 0ff14a413686..08237ce768e3 100644 --- a/packages/docusaurus/src/server/codegen/codegenRoutes.ts +++ b/packages/docusaurus/src/server/codegen/codegenRoutes.ts @@ -101,16 +101,17 @@ function serializeRouteConfig({ }) { const parts = [ `path: '${routePath}'`, - `component: ComponentCreator('${routePath}', '${routeHash}')`, + `Component: ComponentCreator('${routePath}', '${routeHash}')`, ]; + // TODO now useless if (exact) { parts.push(`exact: true`); } if (subroutesCodeStrings) { parts.push( - `routes: [ + `children: [ ${indent(subroutesCodeStrings.join(',\n'))} ]`, ); diff --git a/packages/docusaurus/src/server/plugins/routeConfig.ts b/packages/docusaurus/src/server/plugins/routeConfig.ts index 7832499ecc5d..9431fa6b27f4 100644 --- a/packages/docusaurus/src/server/plugins/routeConfig.ts +++ b/packages/docusaurus/src/server/plugins/routeConfig.ts @@ -15,13 +15,27 @@ import type {RouteConfig} from '@docusaurus/types'; export function applyRouteTrailingSlash( route: Route, params: ApplyTrailingSlashParams, + parent?: Route, ): Route { + let trailingSlash = params.trailingSlash; + + // React Router v8 doesn't like: + // - parent: /docs/ + // - child: /docs + if (parent && parent.path === route.path) { + trailingSlash = true; + } + return { ...route, - path: applyTrailingSlash(route.path, params), + path: applyTrailingSlash(route.path, { + ...params, + trailingSlash, + }), + ...(route.routes && { routes: route.routes.map((subroute) => - applyRouteTrailingSlash(subroute, params), + applyRouteTrailingSlash(subroute, params, route), ), }), }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73e67963d488..0838b26a7bc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -431,14 +431,8 @@ importers: specifier: ^1.0.3 version: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.6))(webpack@5.107.2(@swc/core@1.15.40)(postcss@8.5.15)) react-router: - specifier: ^5.3.4 - version: 5.3.4(react@19.2.6) - react-router-config: - specifier: ^5.1.1 - version: 5.1.1(react-router@5.3.4(react@19.2.6))(react@19.2.6) - react-router-dom: - specifier: ^5.3.4 - version: 5.3.4(react@19.2.6) + specifier: ^8.0.0 + version: 8.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) semver: specifier: ^7.7.4 version: 7.8.1 @@ -485,12 +479,6 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) - '@types/react-router-config': - specifier: ^5.0.7 - version: 5.0.11 - '@types/react-router-dom': - specifier: ^5.3.3 - version: 5.3.3 '@types/serve-handler': specifier: ^6.1.4 version: 6.1.4 @@ -846,12 +834,6 @@ importers: '@types/react': specifier: 19.2.14 version: 19.2.14 - '@types/react-router-config': - specifier: '*' - version: 5.0.11 - '@types/react-router-dom': - specifier: '*' - version: 5.3.3 react: specifier: '*' version: 19.2.6 @@ -864,6 +846,9 @@ importers: react-loadable: specifier: npm:@docusaurus/react-loadable@6.0.0 version: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' + react-router: + specifier: ^8.0.0 + version: 8.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) packages/docusaurus-plugin-client-redirects: dependencies: @@ -1010,9 +995,6 @@ importers: '@docusaurus/utils-validation': specifier: 3.10.1 version: link:../docusaurus-utils-validation - '@types/react-router-config': - specifier: ^5.0.7 - version: 5.0.11 combine-promises: specifier: ^1.1.0 version: 1.2.0 @@ -1591,9 +1573,6 @@ importers: react-dom: specifier: ^19.2.5 version: 19.2.6(react@19.2.6) - react-router-dom: - specifier: ^5.3.4 - version: 5.3.4(react@19.2.6) rtlcss: specifier: ^4.1.0 version: 4.3.0 @@ -1637,9 +1616,6 @@ importers: '@types/react': specifier: 19.2.14 version: 19.2.14 - '@types/react-router-config': - specifier: '*' - version: 5.0.11 clsx: specifier: ^2.0.0 version: 2.1.1 @@ -5955,15 +5931,6 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react-router-config@5.0.11': - resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==} - - '@types/react-router-dom@5.3.3': - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} - - '@types/react-router@5.1.20': - resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} @@ -7143,6 +7110,9 @@ packages: resolution: {integrity: sha512-JtXpxqDqJ8P0UwEHwhxLzCIXQy97vlYBZR222Sbzb1q1Erex9ASrztJ29SyhWFQjod1AeFBaPzEEC8YvtZMIYg==} engines: {node: '>=6'} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} @@ -8694,12 +8664,6 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - history@4.10.1: - resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} - - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} @@ -9237,9 +9201,6 @@ packages: resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==} engines: {node: '>=12'} - isarray@0.0.1: - resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -10644,9 +10605,6 @@ packages: path-to-regexp@0.1.13: resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} - path-to-regexp@1.9.0: - resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} - path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} @@ -11376,21 +11334,15 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} - react-router-config@5.1.1: - resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==} - peerDependencies: - react: '>=15' - react-router: '>=5' - - react-router-dom@5.3.4: - resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==} - peerDependencies: - react: '>=15' - - react-router@5.3.4: - resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==} + react-router@8.0.0: + resolution: {integrity: sha512-AyS7LUn9M3g2/IBJDJNpWqElaQjPVBHGgMsdLGee6B2JLwP9STSpRwN8N4SauOeFTb0X5ZN0wxa1Iek78jF8Iw==} + engines: {node: '>=22.22.0'} peerDependencies: - react: '>=15' + react: '>=19.2.7' + react-dom: '>=19.2.7' + peerDependenciesMeta: + react-dom: + optional: true react@16.14.0: resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==} @@ -12413,12 +12365,6 @@ packages: thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -12830,9 +12776,6 @@ packages: resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} engines: {node: ^18.17.0 || >=20.5.0} - value-equal@1.0.1: - resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -17404,23 +17347,6 @@ snapshots: dependencies: '@types/react': 19.2.14 - '@types/react-router-config@5.0.11': - dependencies: - '@types/history': 4.7.11 - '@types/react': 19.2.14 - '@types/react-router': 5.1.20 - - '@types/react-router-dom@5.3.3': - dependencies: - '@types/history': 4.7.11 - '@types/react': 19.2.14 - '@types/react-router': 5.1.20 - - '@types/react-router@5.1.20': - dependencies: - '@types/history': 4.7.11 - '@types/react': 19.2.14 - '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -18773,6 +18699,8 @@ snapshots: lodash.clonedeep: 4.5.0 yargs-parser: 20.2.9 + cookie-es@3.1.1: {} + cookie-signature@1.0.7: {} cookie@0.7.2: {} @@ -20657,19 +20585,6 @@ snapshots: highlight.js@10.7.3: {} - history@4.10.1: - dependencies: - '@babel/runtime': 7.29.7 - loose-envify: 1.4.0 - resolve-pathname: 3.0.0 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - value-equal: 1.0.1 - - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - hookified@1.15.1: {} hookified@2.2.0: {} @@ -21175,8 +21090,6 @@ snapshots: is-yarn-global@0.4.1: {} - isarray@0.0.1: {} - isarray@1.0.0: {} isarray@2.0.5: {} @@ -23257,10 +23170,6 @@ snapshots: path-to-regexp@0.1.13: {} - path-to-regexp@1.9.0: - dependencies: - isarray: 0.0.1 - path-to-regexp@3.3.0: {} path-type@3.0.0: @@ -23998,35 +23907,12 @@ snapshots: react-refresh@0.18.0: {} - react-router-config@5.1.1(react-router@5.3.4(react@19.2.6))(react@19.2.6): + react-router@8.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@babel/runtime': 7.29.7 + cookie-es: 3.1.1 react: 19.2.6 - react-router: 5.3.4(react@19.2.6) - - react-router-dom@5.3.4(react@19.2.6): - dependencies: - '@babel/runtime': 7.29.7 - history: 4.10.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 19.2.6 - react-router: 5.3.4(react@19.2.6) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - react-router@5.3.4(react@19.2.6): - dependencies: - '@babel/runtime': 7.29.7 - history: 4.10.1 - hoist-non-react-statics: 3.3.2 - loose-envify: 1.4.0 - path-to-regexp: 1.9.0 - prop-types: 15.8.1 - react: 19.2.6 - react-is: 16.13.1 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 + optionalDependencies: + react-dom: 19.2.6(react@19.2.6) react@16.14.0: dependencies: @@ -25360,10 +25246,6 @@ snapshots: thunky@1.1.0: {} - tiny-invariant@1.3.3: {} - - tiny-warning@1.0.3: {} - tinybench@2.9.0: {} tinycolor2@1.6.0: {} @@ -25762,8 +25644,6 @@ snapshots: validate-npm-package-name@6.0.2: {} - value-equal@1.0.1: {} - vary@1.1.2: {} vfile-location@5.0.3: diff --git a/website/_dogfooding/_pages tests/history-tests.tsx b/website/_dogfooding/_pages tests/history-tests.tsx index 7cb8ebfd7248..da9edf48279c 100644 --- a/website/_dogfooding/_pages tests/history-tests.tsx +++ b/website/_dogfooding/_pages tests/history-tests.tsx @@ -4,20 +4,17 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import {useEffect, type ReactNode} from 'react'; -import {useHistory} from '@docusaurus/router'; +import {type ReactNode} from 'react'; +import {useBlocker} from 'react-router'; import Layout from '@theme/Layout'; import Heading from '@theme/Heading'; // Test for https://github.com/facebook/docusaurus/issues/10988 function BlockNavigation() { - const history = useHistory(); - useEffect(() => { - return history.block(() => { - alert('navigation blocked successfully'); - return false; - }); - }, [history]); + useBlocker(() => { + alert('navigation blocked successfully'); + return false; + }); return false; } diff --git a/website/src/components/APITable/index.tsx b/website/src/components/APITable/index.tsx index 3c780740eeb3..059ff56ea278 100644 --- a/website/src/components/APITable/index.tsx +++ b/website/src/components/APITable/index.tsx @@ -13,8 +13,8 @@ import React, { useRef, useEffect, } from 'react'; +import {useLocation, useNavigate} from 'react-router'; import useBrokenLinks from '@docusaurus/useBrokenLinks'; -import {useHistory} from '@docusaurus/router'; import styles from './styles.module.css'; interface Props { @@ -54,13 +54,16 @@ function APITableRow({ const entryName = getRowName(children); const id = name ? `${name}-${entryName}` : entryName; const anchor = `#${id}`; - const history = useHistory(); useBrokenLinks().collectAnchor(id); + + const location = useLocation(); + const navigate = useNavigate(); + return ( { const isTDClick = (e.target as HTMLElement).tagName.toUpperCase() === 'TD'; @@ -68,12 +71,12 @@ function APITableRow({ const shouldNavigate = isTDClick && !hasSelectedText; if (shouldNavigate) { - history.push(anchor); + navigate(anchor); } }} onKeyDown={(e: React.KeyboardEvent) => { if (e.key === 'Enter') { - history.push(anchor); + navigate(anchor); } }}> {children.props.children}