useNavigate.tsx 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import {useCallback, useEffect, useRef} from 'react';
  2. import {useNavigate as useReactRouter6Navigate} from 'react-router-dom';
  3. import type {LocationDescriptor} from 'history';
  4. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  5. import {locationDescriptorToTo} from './reactRouter6Compat/location';
  6. import {useRouteContext} from './useRouteContext';
  7. type NavigateOptions = {
  8. replace?: boolean;
  9. state?: any;
  10. };
  11. interface ReactRouter3Navigate {
  12. (to: LocationDescriptor, options?: NavigateOptions): void;
  13. (delta: number): void;
  14. }
  15. /**
  16. * Returns an imperative method for changing the location. Used by `<Link>`s, but
  17. * may also be used by other elements to change the location.
  18. *
  19. * @see https://reactrouter.com/hooks/use-navigate
  20. */
  21. export function useNavigate(): ReactRouter3Navigate {
  22. // When running in test mode we still read from the legacy route context to
  23. // keep test compatability while we fully migrate to react router 6
  24. const legacyRouterContext = useRouteContext();
  25. if (!legacyRouterContext) {
  26. // biome-ignore lint/correctness/useHookAtTopLevel: react-router-6 migration
  27. const router6Navigate = useReactRouter6Navigate();
  28. // XXX(epurkhiser): Translate legacy LocationDescriptor to To in the
  29. // navigate helper.
  30. // biome-ignore lint/correctness/useHookAtTopLevel: react-router-6 migration
  31. const navigate = useCallback<ReactRouter3Navigate>(
  32. (to: LocationDescriptor | number, options: NavigateOptions = {}) =>
  33. typeof to === 'number'
  34. ? router6Navigate(to)
  35. : router6Navigate(locationDescriptorToTo(to), options),
  36. [router6Navigate]
  37. );
  38. return navigate;
  39. }
  40. // XXX(epurkihser): We are using react-router 3 here, to avoid recursive
  41. // dependencies we just use the useRouteContext instead of useRouter here
  42. const {router} = legacyRouterContext;
  43. // biome-ignore lint/correctness/useHookAtTopLevel: react-router-6 migration
  44. const hasMountedRef = useRef(false);
  45. // biome-ignore lint/correctness/useHookAtTopLevel: react-router-6 migration
  46. useEffect(() => {
  47. hasMountedRef.current = true;
  48. });
  49. // biome-ignore lint/correctness/useHookAtTopLevel: react-router-6 migration
  50. const navigate = useCallback<ReactRouter3Navigate>(
  51. (to: LocationDescriptor | number, options: NavigateOptions = {}) => {
  52. if (!hasMountedRef.current) {
  53. throw new Error(
  54. `You should call navigate() in a React.useEffect(), not when your component is first rendered.`
  55. );
  56. }
  57. if (typeof to === 'number') {
  58. return router.go(to);
  59. }
  60. const normalizedTo = normalizeUrl(to);
  61. const nextState: LocationDescriptor =
  62. typeof normalizedTo === 'string'
  63. ? {
  64. pathname: normalizedTo,
  65. state: options.state,
  66. }
  67. : {
  68. ...normalizedTo,
  69. state: options.state,
  70. };
  71. if (options.replace) {
  72. return router.replace(nextState);
  73. }
  74. return router.push(nextState);
  75. },
  76. [router]
  77. );
  78. return navigate;
  79. }