diff --git a/package.json b/package.json index 159ca4c5f..30e646760 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "react-error-boundary": "6.0.0", "react-hook-form": "7.66.0", "react-popper": "^2.2.5", - "react-router-dom": "6.27.0", + "react-router": "7.13.0", "rxjs": "7.8.2", "use-konami": "^1.0.1", "usehooks-ts": "3.1.1", diff --git a/src/components/ConfirmLeavingPage/ConfirmLeavingPage.test.tsx b/src/components/ConfirmLeavingPage/ConfirmLeavingPage.test.tsx index 62ce1012b..97e82568b 100644 --- a/src/components/ConfirmLeavingPage/ConfirmLeavingPage.test.tsx +++ b/src/components/ConfirmLeavingPage/ConfirmLeavingPage.test.tsx @@ -1,5 +1,5 @@ import React, { PropsWithChildren } from 'react'; -import { Route, Router, Routes } from 'react-router-dom'; +import { Route, Router, Routes } from 'react-router'; import { locationService } from '@grafana/runtime'; import { TextLink } from '@grafana/ui'; import { fireEvent, render, screen } from '@testing-library/react'; diff --git a/src/components/ConfirmLeavingPage/ConfirmLeavingPage.tsx b/src/components/ConfirmLeavingPage/ConfirmLeavingPage.tsx index a1aa38782..ae5c05feb 100644 --- a/src/components/ConfirmLeavingPage/ConfirmLeavingPage.tsx +++ b/src/components/ConfirmLeavingPage/ConfirmLeavingPage.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router'; import { locationService } from '@grafana/runtime'; import { Location } from 'history'; @@ -12,7 +12,7 @@ interface ConfirmLeavingPageProps { } /** - * When enabled is `true`, will block `react-router-dom` navigation with confirm modal. + * When enabled is `true`, will block `react-router` navigation with confirm modal. * Native navigations are handled with native `confirm` * * @example diff --git a/src/components/SceneRedirecter.test.tsx b/src/components/SceneRedirecter.test.tsx index 59e365306..b82cc93d1 100644 --- a/src/components/SceneRedirecter.test.tsx +++ b/src/components/SceneRedirecter.test.tsx @@ -19,7 +19,7 @@ jest.mock('components/RunbookRedirectAlert.utils', () => ({ doRunbookRedirect: jest.fn(), })); -jest.mock('react-router-dom', () => ({ +jest.mock('react-router', () => ({ Navigate: ({ to, replace }: { to: string; replace: boolean }) => (
Navigate to {to} diff --git a/src/components/SceneRedirecter.tsx b/src/components/SceneRedirecter.tsx index d5da0b21e..fe74b3cad 100644 --- a/src/components/SceneRedirecter.tsx +++ b/src/components/SceneRedirecter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Navigate } from 'react-router-dom'; +import { Navigate } from 'react-router'; import { PluginPage } from '@grafana/runtime'; import { CheckAlertType, CheckAlertWithRunbookUrl } from 'types'; diff --git a/src/configPage/PluginConfigPage/PluginConfigPage.test.tsx b/src/configPage/PluginConfigPage/PluginConfigPage.test.tsx index b9c5bdec5..9f586f0c6 100644 --- a/src/configPage/PluginConfigPage/PluginConfigPage.test.tsx +++ b/src/configPage/PluginConfigPage/PluginConfigPage.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { MemoryRouter, Route, Routes } from 'react-router'; import { render, screen, waitFor, within } from '@testing-library/react'; import { SMDataSource } from '../../datasource/DataSource'; diff --git a/src/contexts/SMDatasourceContext.test.tsx b/src/contexts/SMDatasourceContext.test.tsx index 34a0e45ba..217af8c70 100644 --- a/src/contexts/SMDatasourceContext.test.tsx +++ b/src/contexts/SMDatasourceContext.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { MemoryRouter, Route, Routes } from 'react-router'; import { QueryClientProvider } from '@tanstack/react-query'; import { screen } from '@testing-library/react'; import { SM_META } from 'test/fixtures/meta'; @@ -7,6 +7,7 @@ import { ComponentWrapperProps, render } from 'test/render'; import { runTestWithoutSMAccess } from 'test/utils'; import { hasGlobalPermission } from 'utils'; +import { PLUGIN_URL_PATH } from 'routing/constants'; import { getQueryClient } from 'data/queryClient'; import { FeatureFlagProvider } from 'components/FeatureFlagProvider'; @@ -20,14 +21,14 @@ jest.mock('utils', () => { }; }); -const Wrapper = ({ children, initialEntries, meta }: ComponentWrapperProps) => { +const Wrapper = ({ children, initialEntries = [`${PLUGIN_URL_PATH}home`], meta }: ComponentWrapperProps) => { return ( - + diff --git a/src/hooks/useConfirmBeforeUnload.test.ts b/src/hooks/useConfirmBeforeUnload.test.ts index e76a86044..c10ea22b9 100644 --- a/src/hooks/useConfirmBeforeUnload.test.ts +++ b/src/hooks/useConfirmBeforeUnload.test.ts @@ -1,10 +1,10 @@ -import { useBeforeUnload } from 'react-router-dom'; +import { useBeforeUnload } from 'react-router'; import { renderHook } from '@testing-library/react'; import { useConfirmBeforeUnload } from './useConfirmBeforeUnload'; -jest.mock('react-router-dom', () => { - const originalModule = jest.requireActual('react-router-dom'); +jest.mock('react-router', () => { + const originalModule = jest.requireActual('react-router'); return { ...originalModule, useBeforeUnload: jest.fn(), diff --git a/src/hooks/useConfirmBeforeUnload.ts b/src/hooks/useConfirmBeforeUnload.ts index 479f78744..32ed3acb2 100644 --- a/src/hooks/useConfirmBeforeUnload.ts +++ b/src/hooks/useConfirmBeforeUnload.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { useBeforeUnload } from 'react-router-dom'; +import { useBeforeUnload } from 'react-router'; import { useSessionStorage } from 'usehooks-ts'; import { DEV_STORAGE_KEYS } from 'components/DevTools/DevTools.constants'; diff --git a/src/hooks/useQueryParametersState.test.tsx b/src/hooks/useQueryParametersState.test.tsx index 563639bfa..dc2c237b0 100644 --- a/src/hooks/useQueryParametersState.test.tsx +++ b/src/hooks/useQueryParametersState.test.tsx @@ -1,11 +1,11 @@ -import { useLocation as useLocationFromReactRouter } from 'react-router-dom'; +import { useLocation as useLocationFromReactRouter } from 'react-router'; import { locationService } from '@grafana/runtime'; import { act, renderHook } from '@testing-library/react'; import { useQueryParametersState } from './useQueryParametersState'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), +jest.mock('react-router', () => ({ + ...jest.requireActual('react-router'), useLocation: jest.fn(), })); diff --git a/src/hooks/useQueryParametersState.ts b/src/hooks/useQueryParametersState.ts index fad90e338..b767f5910 100644 --- a/src/hooks/useQueryParametersState.ts +++ b/src/hooks/useQueryParametersState.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router'; import { locationService } from '@grafana/runtime'; import { useURLSearchParams } from 'hooks/useURLSearchParams'; diff --git a/src/hooks/useURLSearchParams.ts b/src/hooks/useURLSearchParams.ts index 67fc2821e..91abdd314 100644 --- a/src/hooks/useURLSearchParams.ts +++ b/src/hooks/useURLSearchParams.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router'; export function useURLSearchParams() { const { search } = useLocation(); diff --git a/src/page/CheckList/CheckList.tsx b/src/page/CheckList/CheckList.tsx index e759030f8..ba8bcd8ef 100644 --- a/src/page/CheckList/CheckList.tsx +++ b/src/page/CheckList/CheckList.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { locationService, PluginPage } from '@grafana/runtime'; import { Pagination, useStyles2 } from '@grafana/ui'; diff --git a/src/page/ChecksPage.test.tsx b/src/page/ChecksPage.test.tsx index c4da7e5d5..d10bebafe 100644 --- a/src/page/ChecksPage.test.tsx +++ b/src/page/ChecksPage.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { MemoryRouter, Route, Routes } from 'react-router'; import { QueryClientProvider } from '@tanstack/react-query'; import { screen, waitFor, within } from '@testing-library/react'; import { DataTestIds } from 'test/dataTestIds'; diff --git a/src/page/ConfigPageLayout/ConfigPageLayout.test.tsx b/src/page/ConfigPageLayout/ConfigPageLayout.test.tsx index 07cdc97b5..9672f0cca 100644 --- a/src/page/ConfigPageLayout/ConfigPageLayout.test.tsx +++ b/src/page/ConfigPageLayout/ConfigPageLayout.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { MemoryRouter, Route, Routes } from 'react-router'; import { render } from '@testing-library/react'; import { AppRoutes } from 'routing/types'; diff --git a/src/page/ConfigPageLayout/ConfigPageLayout.tsx b/src/page/ConfigPageLayout/ConfigPageLayout.tsx index 0b3a5c071..0314dcaa5 100644 --- a/src/page/ConfigPageLayout/ConfigPageLayout.tsx +++ b/src/page/ConfigPageLayout/ConfigPageLayout.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { matchPath, Outlet, useLocation } from 'react-router-dom'; +import { matchPath, Outlet, useLocation } from 'react-router'; import { NavModelItem } from '@grafana/data'; import { PluginPage } from '@grafana/runtime'; diff --git a/src/page/DashboardPage.tsx b/src/page/DashboardPage.tsx index 67857d947..f2f17ad5f 100644 --- a/src/page/DashboardPage.tsx +++ b/src/page/DashboardPage.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router'; import { Spinner } from '@grafana/ui'; import { CheckPageParams, CheckType } from 'types'; diff --git a/src/page/EditCheck/EditCheckV2.tsx b/src/page/EditCheck/EditCheckV2.tsx index 3980aaa5b..08164fbab 100644 --- a/src/page/EditCheck/EditCheckV2.tsx +++ b/src/page/EditCheck/EditCheckV2.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router'; import { GrafanaTheme2 } from '@grafana/data'; import { PluginPage } from '@grafana/runtime'; import { Alert, Button, LinkButton, Modal, Text, useStyles2 } from '@grafana/ui'; diff --git a/src/page/EditProbe/EditProbe.tsx b/src/page/EditProbe/EditProbe.tsx index 3ee5bb850..25344eca2 100644 --- a/src/page/EditProbe/EditProbe.tsx +++ b/src/page/EditProbe/EditProbe.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router'; import { locationService, PluginPage } from '@grafana/runtime'; import { LinkButton, TextLink } from '@grafana/ui'; diff --git a/src/page/NewCheck/NewCheckV2.tsx b/src/page/NewCheck/NewCheckV2.tsx index 08b84aa9e..378b06b09 100644 --- a/src/page/NewCheck/NewCheckV2.tsx +++ b/src/page/NewCheck/NewCheckV2.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { useLocation, useParams, useSearchParams } from 'react-router-dom'; +import { useLocation, useParams, useSearchParams } from 'react-router'; import { GrafanaTheme2 } from '@grafana/data'; import { locationService, PluginPage } from '@grafana/runtime'; import { TextLink, useStyles2 } from '@grafana/ui'; diff --git a/src/page/NotFound/CheckNotFound.tsx b/src/page/NotFound/CheckNotFound.tsx index 5a241f71d..337ced308 100644 --- a/src/page/NotFound/CheckNotFound.tsx +++ b/src/page/NotFound/CheckNotFound.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router'; import { TextLink } from '@grafana/ui'; import { createNavModel } from 'utils'; diff --git a/src/routing/InitialisedRouter.tsx b/src/routing/InitialisedRouter.tsx index 51698643f..78764c6ad 100644 --- a/src/routing/InitialisedRouter.tsx +++ b/src/routing/InitialisedRouter.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router'; import { TextLink } from '@grafana/ui'; import { FeatureName } from 'types'; @@ -57,7 +57,7 @@ export const InitialisedRouter = () => { return ( - } /> + } /> { } /> }> } /> - } /> + } /> {/* TODO: Create 404 instead of navigating to home(?) */} - } /> + } /> ); }; diff --git a/src/routing/utils.ts b/src/routing/utils.ts index 1a2495ce2..d14046da2 100644 --- a/src/routing/utils.ts +++ b/src/routing/utils.ts @@ -1,4 +1,4 @@ -import { generatePath, type PathParam } from 'react-router-dom'; +import { generatePath, type PathParam } from 'react-router'; import { CheckType, CheckTypeGroup } from 'types'; import { PLUGIN_URL_PATH } from 'routing/constants'; diff --git a/src/test/helpers/TestRouteInfo.tsx b/src/test/helpers/TestRouteInfo.tsx index 9dbede7a3..9eb338134 100644 --- a/src/test/helpers/TestRouteInfo.tsx +++ b/src/test/helpers/TestRouteInfo.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router'; import { DataTestIds } from '../dataTestIds'; diff --git a/src/test/mocks/@grafana/runtime.tsx b/src/test/mocks/@grafana/runtime.tsx index 7bfbc04fe..c5a6f8223 100644 --- a/src/test/mocks/@grafana/runtime.tsx +++ b/src/test/mocks/@grafana/runtime.tsx @@ -41,7 +41,12 @@ jest.mock('@grafana/runtime', () => { }; const navigate = (path: PathArg, action: string) => { - const next: Location = { ...parsePath(path), state: null, key: Math.random().toString(36).slice(2) }; + const parsed = parsePath(path); + // Prevent infinite loops by skipping navigation to the same location + if (location.pathname === parsed.pathname && location.search === parsed.search) { + return; + } + const next: Location = { ...parsed, state: null, key: Math.random().toString(36).slice(2) }; for (const blocker of blockers) { if (blocker(next, action) === false) { return; diff --git a/src/test/render.tsx b/src/test/render.tsx index 706a02f28..2bfe04f84 100644 --- a/src/test/render.tsx +++ b/src/test/render.tsx @@ -1,5 +1,5 @@ import React, { PropsWithChildren, type ReactElement, type ReactNode } from 'react'; -import { Route, Router, Routes } from 'react-router-dom'; +import { Route, Router, Routes } from 'react-router'; import { QueryClientProvider } from '@tanstack/react-query'; import { AppPluginMeta } from '@grafana/data'; import { locationService, LocationServiceProvider } from '@grafana/runtime'; diff --git a/yarn.lock b/yarn.lock index a6ced082a..3065b2572 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2443,11 +2443,6 @@ redux-thunk "^3.1.0" reselect "^5.1.0" -"@remix-run/router@1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.20.0.tgz#03554155b45d8b529adf635b2f6ad1165d70d8b4" - integrity sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg== - "@remix-run/router@1.23.0": version "1.23.0" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.0.tgz#35390d0e7779626c026b11376da6789eb8389242" @@ -4280,7 +4275,7 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^1.0.2: +cookie@^1.0.1, cookie@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== @@ -9024,14 +9019,6 @@ react-router-dom@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router-dom@6.27.0: - version "6.27.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.27.0.tgz#8d7972a425fd75f91c1e1ff67e47240c5752dc3f" - integrity sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g== - dependencies: - "@remix-run/router" "1.20.0" - react-router "6.27.0" - react-router@5.3.4: version "5.3.4" resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" @@ -9047,13 +9034,6 @@ react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@6.27.0: - version "6.27.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.27.0.tgz#db292474926c814c996c0ff3ef0162d1f9f60ed4" - integrity sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw== - dependencies: - "@remix-run/router" "1.20.0" - react-router@6.30.0: version "6.30.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.0.tgz#9789d775e63bc0df60f39ced77c8c41f1e01ff90" @@ -9061,6 +9041,14 @@ react-router@6.30.0: dependencies: "@remix-run/router" "1.23.0" +react-router@7.13.0: + version "7.13.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.13.0.tgz#de9484aee764f4f65b93275836ff5944d7f5bd3b" + integrity sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw== + dependencies: + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + react-select@5.10.2: version "5.10.2" resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.2.tgz#8dffc69dfd7d74684d9613e6eb27204e3b99e127" @@ -9549,6 +9537,11 @@ serialize-javascript@^6.0.2: dependencies: randombytes "^2.1.0" +set-cookie-parser@^2.6.0: + version "2.7.2" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"