fix(graph): fix search params reload when back to graph (#26580)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> - change useIntervalWhen - when this function is used, it is mostly calling a fetch request, change it to take an async function - wait for the current fetch request to finish before firing the next one - change the name to usePoll - currently, when going back to graph, it needs to remove the search params expand and sometimes need to add ?project= search params - delete expand search params is called after adding the project search params ``` setSearchParams( (currentSearchParams) => { currentSearchParams.delete('expanded'); return currentSearchParams; }, { replace: true, preventScrollReset: true } ); ``` this function does not contain the ?project= search param key, caused the graph page to reload twice - change the function useRouteConstructor to delete the search params `expanded` there ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
89fbde996f
commit
6ebf676b5e
@ -29,7 +29,7 @@ import {
|
|||||||
fetchProjectGraph,
|
fetchProjectGraph,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
useEnvironmentConfig,
|
useEnvironmentConfig,
|
||||||
useIntervalWhen,
|
usePoll,
|
||||||
useRouteConstructor,
|
useRouteConstructor,
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import {
|
import {
|
||||||
@ -295,13 +295,13 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
useIntervalWhen(
|
usePoll(
|
||||||
() => {
|
async () => {
|
||||||
fetchProjectGraph(
|
const response: ProjectGraphClientResponse = await fetchProjectGraph(
|
||||||
projectGraphDataService,
|
projectGraphDataService,
|
||||||
params,
|
params,
|
||||||
environmentConfig.appConfig
|
environmentConfig.appConfig
|
||||||
).then((response: ProjectGraphClientResponse) => {
|
);
|
||||||
if (response.hash === lastHash) {
|
if (response.hash === lastHash) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -312,7 +312,6 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
fileMap: response.fileMap,
|
fileMap: response.fileMap,
|
||||||
});
|
});
|
||||||
setLastHash(response.hash);
|
setLastHash(response.hash);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
environmentConfig.watch
|
environmentConfig.watch
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
fetchProjectGraph,
|
fetchProjectGraph,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
useEnvironmentConfig,
|
useEnvironmentConfig,
|
||||||
useIntervalWhen,
|
usePoll,
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import { Dropdown, Spinner } from '@nx/graph/ui-components';
|
import { Dropdown, Spinner } from '@nx/graph/ui-components';
|
||||||
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph/ui-theme';
|
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph/ui-theme';
|
||||||
@ -71,15 +71,15 @@ export function Shell(): JSX.Element {
|
|||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setErrors(routerErrors);
|
setErrors(routerErrors);
|
||||||
}, [routerErrors]);
|
}, [routerErrors]);
|
||||||
useIntervalWhen(
|
|
||||||
() => {
|
usePoll(
|
||||||
fetchProjectGraph(
|
async () => {
|
||||||
|
const response: ProjectGraphClientResponse = await fetchProjectGraph(
|
||||||
projectGraphDataService,
|
projectGraphDataService,
|
||||||
params,
|
params,
|
||||||
environmentConfig.appConfig
|
environmentConfig.appConfig
|
||||||
).then((response: ProjectGraphClientResponse) => {
|
);
|
||||||
setErrors(response.errors);
|
setErrors(response.errors);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
environmentConfig.watch
|
environmentConfig.watch
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
fetchProjectGraph,
|
fetchProjectGraph,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
useEnvironmentConfig,
|
useEnvironmentConfig,
|
||||||
useIntervalWhen,
|
usePoll,
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import { ErrorRenderer } from '@nx/graph/ui-components';
|
import { ErrorRenderer } from '@nx/graph/ui-components';
|
||||||
import {
|
import {
|
||||||
@ -23,10 +23,13 @@ export function ErrorBoundary() {
|
|||||||
const hasErrorData =
|
const hasErrorData =
|
||||||
isRouteErrorResponse(error) && error.data.errors?.length > 0;
|
isRouteErrorResponse(error) && error.data.errors?.length > 0;
|
||||||
|
|
||||||
useIntervalWhen(
|
usePoll(
|
||||||
async () => {
|
async () => {
|
||||||
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
|
const data = await fetchProjectGraph(
|
||||||
(data) => {
|
projectGraphDataService,
|
||||||
|
params,
|
||||||
|
appConfig
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
isRouteErrorResponse(error) &&
|
isRouteErrorResponse(error) &&
|
||||||
error.data.id === 'project-not-found' &&
|
error.data.id === 'project-not-found' &&
|
||||||
@ -34,9 +37,6 @@ export function ErrorBoundary() {
|
|||||||
) {
|
) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
watch
|
watch
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
fetchProjectGraph,
|
fetchProjectGraph,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
useEnvironmentConfig,
|
useEnvironmentConfig,
|
||||||
useIntervalWhen,
|
usePoll,
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import { ProjectDetailsHeader } from './project-details-header';
|
import { ProjectDetailsHeader } from './project-details-header';
|
||||||
|
|
||||||
@ -35,16 +35,16 @@ export function ProjectDetailsPage() {
|
|||||||
const projectGraphDataService = getProjectGraphDataService();
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
useIntervalWhen(
|
usePoll(
|
||||||
async () => {
|
async () => {
|
||||||
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
|
const data = await fetchProjectGraph(
|
||||||
(data) => {
|
projectGraphDataService,
|
||||||
|
params,
|
||||||
|
appConfig
|
||||||
|
);
|
||||||
if (data?.hash !== hash) {
|
if (data?.hash !== hash) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
watch
|
watch
|
||||||
|
|||||||
@ -50,7 +50,8 @@ export function ProjectDetailsWrapper({
|
|||||||
navigate(
|
navigate(
|
||||||
routeConstructor(
|
routeConstructor(
|
||||||
`/projects/${encodeURIComponent(data.projectName)}`,
|
`/projects/${encodeURIComponent(data.projectName)}`,
|
||||||
true
|
true,
|
||||||
|
['expanded'] // omit expanded targets from search params
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -75,7 +76,8 @@ export function ProjectDetailsWrapper({
|
|||||||
pathname: `/tasks/${encodeURIComponent(data.targetName)}`,
|
pathname: `/tasks/${encodeURIComponent(data.targetName)}`,
|
||||||
search: `?projects=${encodeURIComponent(data.projectName)}`,
|
search: `?projects=${encodeURIComponent(data.projectName)}`,
|
||||||
},
|
},
|
||||||
true
|
true,
|
||||||
|
['expanded'] // omit expanded targets from search params
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -95,9 +97,9 @@ export function ProjectDetailsWrapper({
|
|||||||
|
|
||||||
const updateSearchParams = (
|
const updateSearchParams = (
|
||||||
params: URLSearchParams,
|
params: URLSearchParams,
|
||||||
targetNames: string[]
|
targetNames?: string[]
|
||||||
) => {
|
) => {
|
||||||
if (targetNames.length === 0) {
|
if (!targetNames || targetNames.length === 0) {
|
||||||
params.delete('expanded');
|
params.delete('expanded');
|
||||||
} else {
|
} else {
|
||||||
params.set('expanded', targetNames.join(','));
|
params.set('expanded', targetNames.join(','));
|
||||||
@ -118,13 +120,6 @@ export function ProjectDetailsWrapper({
|
|||||||
if (collapseAllTargets) {
|
if (collapseAllTargets) {
|
||||||
collapseAllTargets();
|
collapseAllTargets();
|
||||||
}
|
}
|
||||||
setSearchParams(
|
|
||||||
(currentSearchParams) => {
|
|
||||||
currentSearchParams.delete('expanded');
|
|
||||||
return currentSearchParams;
|
|
||||||
},
|
|
||||||
{ replace: true, preventScrollReset: true }
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, []); // only run on mount
|
}, []); // only run on mount
|
||||||
|
|
||||||
@ -132,7 +127,7 @@ export function ProjectDetailsWrapper({
|
|||||||
const expandedTargetsParams =
|
const expandedTargetsParams =
|
||||||
searchParams.get('expanded')?.split(',') || [];
|
searchParams.get('expanded')?.split(',') || [];
|
||||||
|
|
||||||
if (expandedTargetsParams.join(',') === expandedTargets.join(',')) {
|
if (expandedTargetsParams.join(',') === expandedTargets?.join(',')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ export * from './lib/external-api-service';
|
|||||||
export * from './lib/use-environment-config';
|
export * from './lib/use-environment-config';
|
||||||
export * from './lib/app-config';
|
export * from './lib/app-config';
|
||||||
export * from './lib/use-route-constructor';
|
export * from './lib/use-route-constructor';
|
||||||
export * from './lib/use-interval-when';
|
export * from './lib/use-poll';
|
||||||
export * from './lib/project-graph-data-service/get-project-graph-data-service';
|
export * from './lib/project-graph-data-service/get-project-graph-data-service';
|
||||||
export * from './lib/fetch-project-graph';
|
export * from './lib/fetch-project-graph';
|
||||||
export * from './lib/error-toast';
|
export * from './lib/error-toast';
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
export const useIntervalWhen = (
|
|
||||||
callback: () => void,
|
|
||||||
delay: number,
|
|
||||||
condition: boolean
|
|
||||||
) => {
|
|
||||||
const savedCallback = useRef(() => {});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (condition) {
|
|
||||||
savedCallback.current = callback;
|
|
||||||
}
|
|
||||||
}, [callback, condition]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (condition) {
|
|
||||||
const tick = () => {
|
|
||||||
savedCallback.current();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (delay !== null) {
|
|
||||||
let id = setInterval(tick, delay);
|
|
||||||
return () => clearInterval(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [delay, condition]);
|
|
||||||
};
|
|
||||||
37
graph/shared/src/lib/use-poll.ts
Normal file
37
graph/shared/src/lib/use-poll.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export const usePoll = (
|
||||||
|
callback: () => Promise<void>,
|
||||||
|
delay: number,
|
||||||
|
condition: boolean
|
||||||
|
) => {
|
||||||
|
const savedCallback = useRef(() => Promise.resolve());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (condition) {
|
||||||
|
savedCallback.current = callback;
|
||||||
|
}
|
||||||
|
}, [callback, condition]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!condition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
|
async function callTickAfterDelay() {
|
||||||
|
await savedCallback.current();
|
||||||
|
if (delay !== null) {
|
||||||
|
timeoutId = setTimeout(callTickAfterDelay, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callTickAfterDelay();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [delay, condition]);
|
||||||
|
};
|
||||||
@ -3,13 +3,23 @@ import { getEnvironmentConfig } from './use-environment-config';
|
|||||||
|
|
||||||
export const useRouteConstructor = (): ((
|
export const useRouteConstructor = (): ((
|
||||||
to: To,
|
to: To,
|
||||||
retainSearchParams: boolean
|
retainSearchParams: boolean,
|
||||||
|
searchParamsKeysToOmit?: string[]
|
||||||
) => To) => {
|
) => To) => {
|
||||||
const { environment } = getEnvironmentConfig();
|
const { environment } = getEnvironmentConfig();
|
||||||
const { selectedWorkspaceId } = useParams();
|
const { selectedWorkspaceId } = useParams();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
return (to: To, retainSearchParams: true) => {
|
return (
|
||||||
|
to: To,
|
||||||
|
retainSearchParams: boolean = true,
|
||||||
|
searchParamsKeysToOmit: string[] = []
|
||||||
|
) => {
|
||||||
|
if (searchParamsKeysToOmit?.length) {
|
||||||
|
searchParamsKeysToOmit.forEach((key) => {
|
||||||
|
searchParams.delete(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
let pathname = '';
|
let pathname = '';
|
||||||
|
|
||||||
if (typeof to === 'object') {
|
if (typeof to === 'object') {
|
||||||
|
|||||||
@ -37,8 +37,8 @@ export class GraphTooltipService {
|
|||||||
if (graph.getTaskInputs) {
|
if (graph.getTaskInputs) {
|
||||||
graph.getTaskInputs(event.data.id).then((inputs) => {
|
graph.getTaskInputs(event.data.id).then((inputs) => {
|
||||||
if (
|
if (
|
||||||
this.currentTooltip.type === 'taskNode' &&
|
this.currentTooltip?.type === 'taskNode' &&
|
||||||
this.currentTooltip.props.id === event.data.id
|
this.currentTooltip?.props.id === event.data.id
|
||||||
) {
|
) {
|
||||||
this.openTaskNodeTooltip(event.ref, {
|
this.openTaskNodeTooltip(event.ref, {
|
||||||
...event.data,
|
...event.data,
|
||||||
|
|||||||
@ -129,14 +129,15 @@ export const TargetConfigurationDetailsHeader = ({
|
|||||||
// TODO: fix tooltip overflow in collapsed state
|
// TODO: fix tooltip overflow in collapsed state
|
||||||
data-tooltip={isCollasped ? false : 'View in Task Graph'}
|
data-tooltip={isCollasped ? false : 'View in Task Graph'}
|
||||||
data-tooltip-align-right
|
data-tooltip-align-right
|
||||||
>
|
|
||||||
<EyeIcon
|
|
||||||
className={`h-5 w-5 !cursor-pointer`}
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
if (isCollasped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onViewInTaskGraph({ projectName, targetName });
|
onViewInTaskGraph({ projectName, targetName });
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<EyeIcon className={`h-5 w-5 !cursor-pointer`} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -249,7 +249,7 @@
|
|||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-redux": "8.0.5",
|
"react-redux": "8.0.5",
|
||||||
"react-refresh": "^0.10.0",
|
"react-refresh": "^0.10.0",
|
||||||
"react-router-dom": "^6.21.2",
|
"react-router-dom": "^6.23.1",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"regenerator-runtime": "0.13.7",
|
"regenerator-runtime": "0.13.7",
|
||||||
"resolve.exports": "1.1.0",
|
"resolve.exports": "1.1.0",
|
||||||
|
|||||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@ -822,8 +822,8 @@ devDependencies:
|
|||||||
specifier: ^0.10.0
|
specifier: ^0.10.0
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.21.2
|
specifier: ^6.23.1
|
||||||
version: 6.21.2(react-dom@18.3.1)(react@18.3.1)
|
version: 6.23.1(react-dom@18.3.1)(react@18.3.1)
|
||||||
react-textarea-autosize:
|
react-textarea-autosize:
|
||||||
specifier: ^8.5.3
|
specifier: ^8.5.3
|
||||||
version: 8.5.3(@types/react@18.3.1)(react@18.3.1)
|
version: 8.5.3(@types/react@18.3.1)(react@18.3.1)
|
||||||
@ -12051,11 +12051,6 @@ packages:
|
|||||||
typescript: 5.4.2
|
typescript: 5.4.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@remix-run/router@1.14.2:
|
|
||||||
resolution: {integrity: sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==}
|
|
||||||
engines: {node: '>=14.0.0'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@remix-run/router@1.15.3:
|
/@remix-run/router@1.15.3:
|
||||||
resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==}
|
resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@ -12066,6 +12061,11 @@ packages:
|
|||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@remix-run/router@1.16.1:
|
||||||
|
resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@remix-run/server-runtime@2.8.1(typescript@5.4.2):
|
/@remix-run/server-runtime@2.8.1(typescript@5.4.2):
|
||||||
resolution: {integrity: sha512-fh4SOEoONrN73Kvzc0gMDCmYpVRVbvoj9j3BUXHAcn0An8iX+HD/22gU7nTkIBzExM/F9xgEcwTewOnWqLw0Bg==}
|
resolution: {integrity: sha512-fh4SOEoONrN73Kvzc0gMDCmYpVRVbvoj9j3BUXHAcn0An8iX+HD/22gU7nTkIBzExM/F9xgEcwTewOnWqLw0Bg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@ -30502,26 +30502,26 @@ packages:
|
|||||||
use-sidecar: 1.1.2(@types/react@18.3.1)(react@18.3.1)
|
use-sidecar: 1.1.2(@types/react@18.3.1)(react@18.3.1)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/react-router-dom@6.21.2(react-dom@18.3.1)(react@18.3.1):
|
/react-router-dom@6.23.1(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==}
|
resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.8'
|
react: '>=16.8'
|
||||||
react-dom: '>=16.8'
|
react-dom: '>=16.8'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@remix-run/router': 1.14.2
|
'@remix-run/router': 1.16.1
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
react-router: 6.21.2(react@18.3.1)
|
react-router: 6.23.1(react@18.3.1)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/react-router@6.21.2(react@18.3.1):
|
/react-router@6.23.1(react@18.3.1):
|
||||||
resolution: {integrity: sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==}
|
resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.8'
|
react: '>=16.8'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@remix-run/router': 1.14.2
|
'@remix-run/router': 1.16.1
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user