urlParamBatchContext.tsx 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import {
  2. createContext,
  3. useCallback,
  4. useContext,
  5. useEffect,
  6. useMemo,
  7. useState,
  8. } from 'react';
  9. import debounce from 'lodash/debounce';
  10. import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import {useNavigate} from 'sentry/utils/useNavigate';
  13. type BatchContextType = {
  14. batchUrlParamUpdates: (updates: Record<string, string | string[] | undefined>) => void;
  15. flushUpdates: () => void;
  16. };
  17. const BatchContext = createContext<BatchContextType | null>(null);
  18. export function UrlParamBatchProvider({children}: {children: React.ReactNode}) {
  19. const navigate = useNavigate();
  20. const location = useLocation();
  21. const [pendingUpdates, setPendingUpdates] = useState<
  22. Record<string, string | string[] | undefined>
  23. >({});
  24. const batchUrlParamUpdates = useCallback(
  25. (updates: Record<string, string | string[] | undefined>) => {
  26. setPendingUpdates(current => ({...current, ...updates}));
  27. },
  28. []
  29. );
  30. const flushUpdates = useCallback(() => {
  31. if (Object.keys(pendingUpdates).length === 0) {
  32. return;
  33. }
  34. navigate(
  35. {
  36. ...location,
  37. query: {
  38. ...location.query,
  39. ...pendingUpdates,
  40. },
  41. },
  42. // TODO: Use replace until we can sync the state of the widget
  43. // when the user navigates back
  44. {replace: true}
  45. );
  46. setPendingUpdates({});
  47. }, [location, navigate, pendingUpdates]);
  48. // Debounce URL updates
  49. const updateURL = useMemo(
  50. () =>
  51. debounce(() => {
  52. flushUpdates();
  53. }, DEFAULT_DEBOUNCE_DURATION),
  54. [flushUpdates]
  55. );
  56. // Trigger the URL updates
  57. useEffect(() => {
  58. updateURL();
  59. return () => updateURL.cancel();
  60. }, [updateURL]);
  61. return (
  62. <BatchContext.Provider value={{batchUrlParamUpdates, flushUpdates}}>
  63. {children}
  64. </BatchContext.Provider>
  65. );
  66. }
  67. export const useUrlBatchContext = () => {
  68. const context = useContext(BatchContext);
  69. if (!context) {
  70. throw new Error('useUrlBatchContext must be used within a UrlParamBatchProvider');
  71. }
  72. return context;
  73. };