traceStateProvider.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import type React from 'react';
  2. import {createContext, useContext, useLayoutEffect, useMemo} from 'react';
  3. import * as qs from 'query-string';
  4. import {t} from 'sentry/locale';
  5. import {
  6. type DispatchingReducerEmitter,
  7. useDispatchingReducer,
  8. } from 'sentry/utils/useDispatchingReducer';
  9. import {TraceReducer, type TraceReducerAction, type TraceReducerState} from './index';
  10. import {storeTraceViewPreferences, type TracePreferencesState} from './tracePreferences';
  11. interface TraceStateContext {}
  12. export const TraceStateContext = createContext<TraceReducerState | null>(null);
  13. export const TraceStateDispatchContext =
  14. createContext<React.Dispatch<TraceReducerAction> | null>(null);
  15. export const TraceStateEmitterContext = createContext<DispatchingReducerEmitter<
  16. typeof TraceReducer
  17. > | null>(null);
  18. export function useTraceState(): TraceReducerState {
  19. const context = useContext(TraceStateContext);
  20. if (!context) {
  21. throw new Error('useTraceState must be used within a TraceStateProvider');
  22. }
  23. return context;
  24. }
  25. export function useTraceStateDispatch(): React.Dispatch<TraceReducerAction> {
  26. const context = useContext(TraceStateDispatchContext);
  27. if (!context) {
  28. throw new Error('useTraceStateDispatch must be used within a TraceStateProvider');
  29. }
  30. return context;
  31. }
  32. export function useTraceStateEmitter(): DispatchingReducerEmitter<typeof TraceReducer> {
  33. const context = useContext(TraceStateEmitterContext);
  34. if (!context) {
  35. throw new Error('useTraceStateEmitter must be used within a TraceStateProvider');
  36. }
  37. return context;
  38. }
  39. const TRACE_TAB: TraceReducerState['tabs']['tabs'][0] = {
  40. node: 'trace',
  41. label: t('Trace'),
  42. };
  43. const STATIC_DRAWER_TABS: TraceReducerState['tabs']['tabs'] = [TRACE_TAB];
  44. interface TraceStateProviderProps {
  45. children: React.ReactNode;
  46. initialPreferences: TracePreferencesState;
  47. preferencesStorageKey?: string;
  48. }
  49. export function TraceStateProvider(props: TraceStateProviderProps): React.ReactNode {
  50. const initialQuery = useMemo((): string | undefined => {
  51. const query = qs.parse(location.search);
  52. if (typeof query.search === 'string') {
  53. return query.search;
  54. }
  55. return undefined;
  56. // We only want to decode on load
  57. // eslint-disable-next-line react-hooks/exhaustive-deps
  58. }, []);
  59. const [traceState, traceDispatch, traceStateEmitter] = useDispatchingReducer(
  60. TraceReducer,
  61. {
  62. rovingTabIndex: {
  63. index: null,
  64. items: null,
  65. node: null,
  66. },
  67. search: {
  68. node: null,
  69. query: initialQuery,
  70. resultIteratorIndex: null,
  71. resultIndex: null,
  72. results: null,
  73. status: undefined,
  74. resultsLookup: new Map(),
  75. },
  76. preferences: props.initialPreferences,
  77. tabs: {
  78. tabs: STATIC_DRAWER_TABS,
  79. current_tab: STATIC_DRAWER_TABS[0] ?? null,
  80. last_clicked_tab: null,
  81. },
  82. }
  83. );
  84. useLayoutEffect(() => {
  85. if (props.preferencesStorageKey) {
  86. storeTraceViewPreferences(props.preferencesStorageKey, traceState.preferences);
  87. }
  88. }, [traceState.preferences, props.preferencesStorageKey]);
  89. return (
  90. <TraceStateContext.Provider value={traceState}>
  91. <TraceStateDispatchContext.Provider value={traceDispatch}>
  92. <TraceStateEmitterContext.Provider value={traceStateEmitter}>
  93. {props.children}
  94. </TraceStateEmitterContext.Provider>
  95. </TraceStateDispatchContext.Provider>
  96. </TraceStateContext.Provider>
  97. );
  98. }