@parca/profile 0.19.140 → 0.19.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/dist/GraphTooltipArrow/Content.js +224 -30
  3. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +192 -33
  4. package/dist/GraphTooltipArrow/ExpandOnHoverValue.js +53 -3
  5. package/dist/GraphTooltipArrow/index.d.ts.map +1 -1
  6. package/dist/GraphTooltipArrow/index.js +86 -56
  7. package/dist/GraphTooltipArrow/useGraphTooltip/index.js +37 -37
  8. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.d.ts.map +1 -1
  9. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +104 -72
  10. package/dist/MatchersInput/SuggestionItem.js +91 -12
  11. package/dist/MatchersInput/SuggestionsList.d.ts +2 -1
  12. package/dist/MatchersInput/SuggestionsList.d.ts.map +1 -1
  13. package/dist/MatchersInput/SuggestionsList.js +371 -157
  14. package/dist/MatchersInput/SuggestionsList.test.d.ts +2 -0
  15. package/dist/MatchersInput/SuggestionsList.test.d.ts.map +1 -0
  16. package/dist/MatchersInput/index.js +308 -115
  17. package/dist/MetricsCircle/index.js +39 -3
  18. package/dist/MetricsGraph/MetricsContextMenu/index.js +119 -19
  19. package/dist/MetricsGraph/MetricsInfoPanel/index.js +81 -20
  20. package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
  21. package/dist/MetricsGraph/MetricsTooltip/index.js +107 -74
  22. package/dist/MetricsGraph/index.js +552 -203
  23. package/dist/MetricsGraph/useMetricsGraphDimensions.js +46 -25
  24. package/dist/MetricsGraph/utils/colorMapping.js +24 -17
  25. package/dist/MetricsSeries/index.js +70 -7
  26. package/dist/PreSelectedMatchers/index.d.ts.map +1 -1
  27. package/dist/PreSelectedMatchers/index.js +249 -102
  28. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
  29. package/dist/ProfileExplorer/ProfileExplorerCompare.js +241 -45
  30. package/dist/ProfileExplorer/ProfileExplorerSingle.js +98 -11
  31. package/dist/ProfileExplorer/index.js +183 -32
  32. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.js +333 -148
  33. package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js +69 -35
  34. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts +2 -2
  35. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -1
  36. package/dist/ProfileFlameChart/SamplesStrips/index.js +645 -134
  37. package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.js +114 -55
  38. package/dist/ProfileFlameChart/index.d.ts.map +1 -1
  39. package/dist/ProfileFlameChart/index.js +267 -129
  40. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
  41. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +288 -89
  42. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.js +56 -20
  43. package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +211 -140
  44. package/dist/ProfileFlameGraph/FlameGraphArrow/MemoizedTooltip.js +133 -38
  45. package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.js +261 -216
  46. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
  47. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +72 -47
  48. package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.d.ts.map +1 -1
  49. package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.js +58 -28
  50. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.d.ts.map +1 -1
  51. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.js +59 -8
  52. package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +396 -179
  53. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts.map +1 -1
  54. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.js +68 -50
  55. package/dist/ProfileFlameGraph/FlameGraphArrow/useMappingList.js +62 -38
  56. package/dist/ProfileFlameGraph/FlameGraphArrow/useNodeColor.js +14 -6
  57. package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +124 -82
  58. package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +160 -98
  59. package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.js +232 -112
  60. package/dist/ProfileFlameGraph/FlameGraphArrow/utils.js +137 -114
  61. package/dist/ProfileFlameGraph/benchmarks/benchdata/populateData.js +85 -0
  62. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  63. package/dist/ProfileFlameGraph/index.js +324 -150
  64. package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +140 -32
  65. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  66. package/dist/ProfileMetricsGraph/index.js +519 -258
  67. package/dist/ProfileSelector/CompareButton.js +132 -12
  68. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  69. package/dist/ProfileSelector/MetricsGraphSection.js +236 -64
  70. package/dist/ProfileSelector/index.d.ts.map +1 -1
  71. package/dist/ProfileSelector/index.js +727 -141
  72. package/dist/ProfileSelector/useAutoQuerySelector.js +249 -130
  73. package/dist/ProfileSource.js +230 -163
  74. package/dist/ProfileTypeSelector/index.js +214 -125
  75. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +50 -4
  76. package/dist/ProfileView/components/ActionButtons/SortByDropdown.d.ts.map +1 -1
  77. package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +141 -35
  78. package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
  79. package/dist/ProfileView/components/ColorStackLegend.js +185 -55
  80. package/dist/ProfileView/components/DashboardItems/index.js +87 -28
  81. package/dist/ProfileView/components/DashboardLayout/index.js +108 -16
  82. package/dist/ProfileView/components/DiffLegend.js +172 -29
  83. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +199 -55
  84. package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -1
  85. package/dist/ProfileView/components/InvertCallStack/index.js +100 -12
  86. package/dist/ProfileView/components/ProfileFilters/filterPresets.js +260 -315
  87. package/dist/ProfileView/components/ProfileFilters/index.js +518 -215
  88. package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +370 -306
  89. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +2 -1
  90. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
  91. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +188 -118
  92. package/dist/ProfileView/components/ProfileHeader/index.js +105 -11
  93. package/dist/ProfileView/components/ShareButton/ResultBox.js +119 -16
  94. package/dist/ProfileView/components/ShareButton/index.js +352 -62
  95. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  96. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +678 -194
  97. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +94 -7
  98. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  99. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +199 -157
  100. package/dist/ProfileView/components/Toolbars/index.d.ts +2 -2
  101. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  102. package/dist/ProfileView/components/Toolbars/index.js +441 -21
  103. package/dist/ProfileView/components/ViewSelector/Dropdown.js +233 -22
  104. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  105. package/dist/ProfileView/components/ViewSelector/index.js +212 -86
  106. package/dist/ProfileView/components/VisualizationContainer/index.d.ts.map +1 -1
  107. package/dist/ProfileView/components/VisualizationContainer/index.js +52 -7
  108. package/dist/ProfileView/components/VisualizationPanel.js +185 -8
  109. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  110. package/dist/ProfileView/context/DashboardContext.js +85 -29
  111. package/dist/ProfileView/context/ProfileViewContext.js +56 -15
  112. package/dist/ProfileView/hooks/useAutoSelectDimension.js +71 -41
  113. package/dist/ProfileView/hooks/useProfileMetadata.js +50 -18
  114. package/dist/ProfileView/hooks/useResetFlameGraphState.d.ts.map +1 -1
  115. package/dist/ProfileView/hooks/useResetFlameGraphState.js +32 -12
  116. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  117. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +71 -27
  118. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.d.ts.map +1 -1
  119. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +40 -19
  120. package/dist/ProfileView/hooks/useVisualizationState.d.ts +3 -3
  121. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  122. package/dist/ProfileView/hooks/useVisualizationState.js +258 -67
  123. package/dist/ProfileView/index.js +383 -45
  124. package/dist/ProfileView/types/visualization.js +1 -13
  125. package/dist/ProfileView/utils/colorUtils.js +8 -7
  126. package/dist/ProfileViewWithData.d.ts.map +1 -1
  127. package/dist/ProfileViewWithData.js +332 -228
  128. package/dist/QueryControls/index.js +418 -47
  129. package/dist/Sandwich/components/CalleesSection.js +54 -4
  130. package/dist/Sandwich/components/CallersSection.js +97 -27
  131. package/dist/Sandwich/components/TableSection.js +77 -4
  132. package/dist/Sandwich/index.d.ts.map +1 -1
  133. package/dist/Sandwich/index.js +126 -14
  134. package/dist/Sandwich/utils/processRowData.js +48 -39
  135. package/dist/SelectWithRefresh/index.js +102 -28
  136. package/dist/SimpleMatchers/Select.js +520 -187
  137. package/dist/SimpleMatchers/index.js +590 -288
  138. package/dist/SourceView/Highlighter.js +230 -70
  139. package/dist/SourceView/LineNo.js +72 -17
  140. package/dist/SourceView/index.d.ts.map +1 -1
  141. package/dist/SourceView/index.js +178 -104
  142. package/dist/SourceView/lang-detector/ext-to-lang.json +798 -798
  143. package/dist/SourceView/lang-detector/index.js +28 -14
  144. package/dist/SourceView/useSelectedLineRange.d.ts.map +1 -1
  145. package/dist/SourceView/useSelectedLineRange.js +99 -23
  146. package/dist/Table/ColorCell.js +42 -1
  147. package/dist/Table/ColumnsVisibility.js +114 -6
  148. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  149. package/dist/Table/MoreDropdown.js +122 -25
  150. package/dist/Table/TableContextMenu.d.ts.map +1 -1
  151. package/dist/Table/TableContextMenu.js +151 -137
  152. package/dist/Table/TableContextMenuWrapper.js +59 -14
  153. package/dist/Table/hooks/useColorManagement.js +58 -16
  154. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  155. package/dist/Table/hooks/useTableConfiguration.js +333 -169
  156. package/dist/Table/index.d.ts.map +1 -1
  157. package/dist/Table/index.js +222 -128
  158. package/dist/Table/utils/functions.js +169 -144
  159. package/dist/Table/utils/topAndBottomExpandedRowModel.js +69 -52
  160. package/dist/TimelineGuide/index.js +209 -16
  161. package/dist/TopTable/benchmarks/benchdata/populateData.js +91 -0
  162. package/dist/TopTable/index.d.ts.map +1 -1
  163. package/dist/TopTable/index.js +342 -123
  164. package/dist/contexts/LabelsQueryProvider.js +94 -32
  165. package/dist/contexts/UnifiedLabelsContext.js +114 -49
  166. package/dist/contexts/utils.js +37 -15
  167. package/dist/hooks/useCompareModeMeta.d.ts.map +1 -1
  168. package/dist/hooks/useCompareModeMeta.js +158 -64
  169. package/dist/hooks/useLabels.js +295 -52
  170. package/dist/hooks/useQueryState.d.ts +3 -3
  171. package/dist/hooks/useQueryState.d.ts.map +1 -1
  172. package/dist/hooks/useQueryState.js +373 -332
  173. package/dist/index.d.ts +2 -3
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +22 -8
  176. package/dist/testdata/fg-diff.json +3750 -0
  177. package/dist/testdata/fg-simple.json +1879 -0
  178. package/dist/testdata/link_data.json +56 -0
  179. package/dist/testdata/tabular.json +30 -0
  180. package/dist/testdata/test_flamegraph.json +26846 -0
  181. package/dist/testdata/test_graph.json +53 -0
  182. package/dist/useDelayedLoader.js +32 -18
  183. package/dist/useGrpcQuery/index.js +71 -11
  184. package/dist/useHasProfileData.js +90 -12
  185. package/dist/useQuery.js +205 -64
  186. package/dist/useSumBy.d.ts +1 -1
  187. package/dist/useSumBy.d.ts.map +1 -1
  188. package/dist/useSumBy.js +294 -138
  189. package/dist/utils.js +62 -30
  190. package/package.json +9 -10
  191. package/src/GraphTooltipArrow/index.tsx +3 -0
  192. package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +13 -11
  193. package/src/MatchersInput/SuggestionsList.test.tsx +70 -0
  194. package/src/MatchersInput/SuggestionsList.tsx +11 -10
  195. package/src/MatchersInput/index.tsx +1 -1
  196. package/src/MetricsGraph/MetricsTooltip/index.tsx +22 -34
  197. package/src/PreSelectedMatchers/index.tsx +3 -0
  198. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +9 -4
  199. package/src/ProfileFlameChart/SamplesStrips/index.tsx +2 -2
  200. package/src/ProfileFlameChart/index.tsx +28 -21
  201. package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +9 -10
  202. package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +6 -5
  203. package/src/ProfileFlameGraph/FlameGraphArrow/TooltipContext.tsx +3 -0
  204. package/src/ProfileFlameGraph/FlameGraphArrow/ZoomControls.tsx +3 -0
  205. package/src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts +3 -0
  206. package/src/ProfileFlameGraph/index.tsx +9 -6
  207. package/src/ProfileMetricsGraph/index.tsx +8 -6
  208. package/src/ProfileSelector/MetricsGraphSection.tsx +10 -5
  209. package/src/ProfileSelector/index.tsx +61 -39
  210. package/src/ProfileView/components/ActionButtons/SortByDropdown.tsx +6 -10
  211. package/src/ProfileView/components/ColorStackLegend.tsx +4 -2
  212. package/src/ProfileView/components/InvertCallStack/index.tsx +4 -5
  213. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +192 -94
  214. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +21 -21
  215. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +28 -24
  216. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +5 -4
  217. package/src/ProfileView/components/Toolbars/index.tsx +3 -3
  218. package/src/ProfileView/components/ViewSelector/index.tsx +16 -9
  219. package/src/ProfileView/components/VisualizationContainer/index.tsx +3 -0
  220. package/src/ProfileView/context/DashboardContext.tsx +6 -6
  221. package/src/ProfileView/hooks/useResetFlameGraphState.ts +4 -6
  222. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +26 -24
  223. package/src/ProfileView/hooks/useResetStateOnSeriesChange.ts +8 -16
  224. package/src/ProfileView/hooks/useVisualizationState.ts +69 -61
  225. package/src/ProfileViewWithData.tsx +35 -29
  226. package/src/Sandwich/index.tsx +3 -4
  227. package/src/SourceView/index.tsx +2 -4
  228. package/src/SourceView/useSelectedLineRange.ts +19 -34
  229. package/src/Table/MoreDropdown.tsx +11 -9
  230. package/src/Table/TableContextMenu.tsx +13 -10
  231. package/src/Table/hooks/useTableConfiguration.tsx +11 -16
  232. package/src/Table/index.tsx +21 -12
  233. package/src/TopTable/index.tsx +4 -3
  234. package/src/hooks/useCompareModeMeta.ts +91 -61
  235. package/src/hooks/useQueryState.test.tsx +345 -275
  236. package/src/hooks/useQueryState.ts +118 -136
  237. package/src/index.tsx +15 -16
  238. package/src/useDelayedLoader.ts +10 -10
  239. package/src/useSumBy.ts +15 -21
  240. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +0 -455
  241. package/dist/hooks/urlParsers.d.ts +0 -18
  242. package/dist/hooks/urlParsers.d.ts.map +0 -1
  243. package/dist/hooks/urlParsers.js +0 -32
  244. package/dist/hooks/useColorBy.d.ts +0 -5
  245. package/dist/hooks/useColorBy.d.ts.map +0 -1
  246. package/dist/hooks/useColorBy.js +0 -26
  247. package/dist/hooks/useDashboardItems.d.ts +0 -5
  248. package/dist/hooks/useDashboardItems.d.ts.map +0 -1
  249. package/dist/hooks/useDashboardItems.js +0 -27
  250. package/dist/hooks/useQueryState.test.js +0 -868
  251. package/src/hooks/urlParsers.ts +0 -38
  252. package/src/hooks/useColorBy.ts +0 -42
  253. package/src/hooks/useDashboardItems.ts +0 -46
@@ -14,15 +14,14 @@
14
14
  import {useEffect, useMemo, useState} from 'react';
15
15
 
16
16
  import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
17
- import {useQueryState} from 'nuqs';
18
17
 
18
+ import {useURLState} from '@parca/components';
19
19
  import {ProfileType} from '@parca/parser';
20
20
  import {valueFormatter} from '@parca/utilities';
21
21
 
22
22
  import {Row} from '../../../Table';
23
23
  import ColumnsVisibility from '../../../Table/ColumnsVisibility';
24
24
  import {ColumnName, addPlusSign, getRatioString} from '../../../Table/utils/functions';
25
- import {tableColumnsParser} from '../../../hooks/urlParsers';
26
25
  import {useProfileViewContext} from '../../context/ProfileViewContext';
27
26
 
28
27
  interface Props {
@@ -33,7 +32,9 @@ interface Props {
33
32
 
34
33
  const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Element => {
35
34
  const {compareMode} = useProfileViewContext();
36
- const [tableColumns, setTableColumns] = useQueryState('table_columns', tableColumnsParser);
35
+ const [tableColumns, setTableColumns] = useURLState<string[]>('table_columns', {
36
+ alwaysReturnArray: true,
37
+ });
37
38
 
38
39
  const columnHelper = createColumnHelper<Row>();
39
40
 
@@ -189,7 +190,7 @@ const TableColumnsDropdown = ({profileType, total, filtered}: Props): JSX.Elemen
189
190
  const newTableColumns = (Object.keys(updatedColumns) as ColumnName[]).filter(
190
191
  col => updatedColumns[col]
191
192
  );
192
- void setTableColumns(newTableColumns);
193
+ setTableColumns(newTableColumns);
193
194
  };
194
195
 
195
196
  return (
@@ -50,7 +50,7 @@ export interface VisualisationToolbarProps {
50
50
  flamechartDimension: string[];
51
51
  setFlamechartDimension: (labels: string[]) => void;
52
52
  showVisualizationSelector?: boolean;
53
- sandwichFunctionName: string | null;
53
+ sandwichFunctionName?: string;
54
54
  alignFunctionName: string;
55
55
  setAlignFunctionName: (align: string) => void;
56
56
  colorBy: string;
@@ -75,7 +75,7 @@ export interface FlameGraphToolbarProps {
75
75
 
76
76
  export interface SandwichFlameGraphToolbarProps {
77
77
  resetSandwichFunctionName: () => void;
78
- sandwichFunctionName: string | null;
78
+ sandwichFunctionName?: string;
79
79
  }
80
80
 
81
81
  export const TableToolbar: FC<TableToolbarProps> = ({profileType, total, filtered}) => {
@@ -120,7 +120,7 @@ export const SandwichFlameGraphToolbar: FC<SandwichFlameGraphToolbarProps> = ({
120
120
  onClick={() => resetSandwichFunctionName()}
121
121
  className="w-auto"
122
122
  variant="neutral"
123
- disabled={sandwichFunctionName == null || sandwichFunctionName.length === 0}
123
+ disabled={sandwichFunctionName === undefined || sandwichFunctionName.length === 0}
124
124
  >
125
125
  Reset view
126
126
  </Button>
@@ -13,13 +13,9 @@
13
13
 
14
14
  import {ReactNode} from 'react';
15
15
 
16
- import {useQueryState} from 'nuqs';
17
-
18
- import {useParcaContext} from '@parca/components';
16
+ import {useParcaContext, useURLState, useURLStateBatch} from '@parca/components';
19
17
 
20
18
  import {ProfileSource} from '../../../ProfileSource';
21
- import {stringParam} from '../../../hooks/urlParsers';
22
- import {useDashboardItems} from '../../../hooks/useDashboardItems';
23
19
  import Dropdown, {DropdownElement, InnerAction} from './Dropdown';
24
20
 
25
21
  interface Props {
@@ -27,9 +23,15 @@ interface Props {
27
23
  }
28
24
 
29
25
  const ViewSelector = ({profileSource}: Props): JSX.Element => {
30
- const {dashboardItems, setDashboardItems} = useDashboardItems();
31
- const [, setSandwichFunctionName] = useQueryState('sandwich_function_name', stringParam);
26
+ const [dashboardItems = ['flamegraph'], setDashboardItems] = useURLState<string[]>(
27
+ 'dashboard_items',
28
+ {
29
+ alwaysReturnArray: true,
30
+ }
31
+ );
32
+ const [, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
32
33
  const {enableSourcesView, enableSandwichView} = useParcaContext();
34
+ const batchUpdates = useURLStateBatch();
33
35
 
34
36
  const allItems: Array<{
35
37
  key: string;
@@ -127,9 +129,14 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
127
129
  } else {
128
130
  const newDashboardItems = dashboardItems.filter(v => v !== item.key);
129
131
 
130
- setDashboardItems(newDashboardItems);
132
+ // Batch updates when removing sandwich panel to combine both URL changes
131
133
  if (item.key === 'sandwich') {
132
- void setSandwichFunctionName(null);
134
+ batchUpdates(() => {
135
+ setDashboardItems(newDashboardItems);
136
+ setSandwichFunctionName(undefined);
137
+ });
138
+ } else {
139
+ setDashboardItems(newDashboardItems);
133
140
  }
134
141
  }
135
142
  },
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/refs */
15
+
14
16
  import {FC} from 'react';
15
17
 
16
18
  import cx from 'classnames';
@@ -42,6 +44,7 @@ export const VisualizationContainer: FC<VisualizationContainerProps> = ({
42
44
  index,
43
45
  actionButtons,
44
46
  }) => {
47
+ 'use no memo';
45
48
  const {handleClosePanel} = useDashboard();
46
49
 
47
50
  return (
@@ -13,10 +13,8 @@
13
13
 
14
14
  import {FC, PropsWithChildren, createContext, useContext} from 'react';
15
15
 
16
- import {useQueryState} from 'nuqs';
16
+ import {useURLState} from '@parca/components';
17
17
 
18
- import {stringParam} from '../../hooks/urlParsers';
19
- import {useDashboardItems} from '../../hooks/useDashboardItems';
20
18
  import {VisualizationType} from '../types/visualization';
21
19
 
22
20
  interface DashboardContextType {
@@ -29,8 +27,10 @@ interface DashboardContextType {
29
27
  const DashboardContext = createContext<DashboardContextType | undefined>(undefined);
30
28
 
31
29
  export const DashboardProvider: FC<PropsWithChildren> = ({children}) => {
32
- const {dashboardItems, setDashboardItems} = useDashboardItems();
33
- const [, setSandwichFunctionName] = useQueryState('sandwich_function_name', stringParam);
30
+ const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
31
+ alwaysReturnArray: true,
32
+ });
33
+ const [, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
34
34
 
35
35
  const handleClosePanel = (visualizationType: VisualizationType): void => {
36
36
  const newDashboardItems = dashboardItems.filter(item => item !== visualizationType);
@@ -38,7 +38,7 @@ export const DashboardProvider: FC<PropsWithChildren> = ({children}) => {
38
38
 
39
39
  // Reset sandwich function name when closing sandwich panel
40
40
  if (visualizationType === 'sandwich') {
41
- void setSandwichFunctionName(null);
41
+ setSandwichFunctionName(undefined);
42
42
  }
43
43
  };
44
44
 
@@ -11,19 +11,17 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useQueryState} from 'nuqs';
15
-
16
- import {stringParam} from '../../hooks/urlParsers';
14
+ import {useURLState} from '@parca/components';
17
15
 
18
16
  export const useResetFlameGraphState = (): (() => void) => {
19
- const [val, setCurPath] = useQueryState('cur_path', stringParam);
17
+ const [val, setCurPath] = useURLState('cur_path');
20
18
 
21
19
  return () => {
22
20
  setTimeout(() => {
23
- if (val === null) {
21
+ if (val === undefined) {
24
22
  return;
25
23
  }
26
- void setCurPath(null);
24
+ setCurPath(undefined);
27
25
  });
28
26
  };
29
27
  };
@@ -11,37 +11,39 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useQueryStates} from 'nuqs';
14
+ import {useURLState, useURLStateBatch} from '@parca/components';
15
15
 
16
- import {stringParam} from '../../hooks/urlParsers';
17
16
  import {useProfileFilters} from '../components/ProfileFilters/useProfileFilters';
18
17
 
19
18
  export const useResetStateOnProfileTypeChange = (): (() => void) => {
20
- const [state, setState] = useQueryStates(
21
- {
22
- group_by: stringParam,
23
- cur_path: stringParam,
24
- sum_by_a: stringParam,
25
- sum_by_b: stringParam,
26
- sandwich_function_name: stringParam,
27
- },
28
- {history: 'replace'}
29
- );
19
+ const [groupBy, setGroupBy] = useURLState('group_by');
20
+ const [curPath, setCurPath] = useURLState('cur_path');
21
+ const [sumByA, setSumByA] = useURLState('sum_by_a');
22
+ const [sumByB, setSumByB] = useURLState('sum_by_b');
30
23
  const {resetFilters} = useProfileFilters();
24
+ const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
25
+ const batchUpdates = useURLStateBatch();
31
26
 
32
27
  return () => {
33
- // Atomic reset: clear all params in single URL update
34
- const updates: Record<string, null> = {};
35
- if (state.group_by !== null) updates.group_by = null;
36
- if (state.cur_path !== null) updates.cur_path = null;
37
- if (state.sandwich_function_name !== null) updates.sandwich_function_name = null;
38
- if (state.sum_by_a !== null) updates.sum_by_a = null;
39
- if (state.sum_by_b !== null) updates.sum_by_b = null;
28
+ // Batch all URL state resets into a single navigation
29
+ batchUpdates(() => {
30
+ if (groupBy !== undefined) {
31
+ setGroupBy(undefined);
32
+ }
33
+ if (curPath !== undefined) {
34
+ setCurPath(undefined);
35
+ }
36
+ if (sandwichFunctionName !== undefined) {
37
+ setSandwichFunctionName(undefined);
38
+ }
39
+ if (sumByA !== undefined) {
40
+ setSumByA(undefined);
41
+ }
42
+ if (sumByB !== undefined) {
43
+ setSumByB(undefined);
44
+ }
40
45
 
41
- if (Object.keys(updates).length > 0) {
42
- void setState(updates);
43
- }
44
-
45
- resetFilters();
46
+ resetFilters();
47
+ });
46
48
  };
47
49
  };
@@ -11,27 +11,19 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useQueryStates} from 'nuqs';
15
-
16
- import {stringParam} from '../../hooks/urlParsers';
14
+ import {useURLState} from '@parca/components';
17
15
 
18
16
  export const useResetStateOnSeriesChange = (): (() => void) => {
19
- const [state, setState] = useQueryStates(
20
- {
21
- cur_path: stringParam,
22
- sandwich_function_name: stringParam,
23
- },
24
- {history: 'replace'}
25
- );
17
+ const [curPath, setCurPath] = useURLState('cur_path');
18
+ const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
26
19
 
27
20
  return () => {
28
21
  setTimeout(() => {
29
- const updates: Record<string, null> = {};
30
- if (state.cur_path !== null) updates.cur_path = null;
31
- if (state.sandwich_function_name !== null) updates.sandwich_function_name = null;
32
-
33
- if (Object.keys(updates).length > 0) {
34
- void setState(updates);
22
+ if (curPath !== undefined) {
23
+ setCurPath(undefined);
24
+ }
25
+ if (sandwichFunctionName !== undefined) {
26
+ setSandwichFunctionName(undefined);
35
27
  }
36
28
  });
37
29
  };
@@ -13,8 +13,13 @@
13
13
 
14
14
  import {useCallback, useMemo} from 'react';
15
15
 
16
- import {useQueryState} from 'nuqs';
17
-
16
+ import {
17
+ JSONParser,
18
+ JSONSerializer,
19
+ useURLState,
20
+ useURLStateBatch,
21
+ useURLStateCustom,
22
+ } from '@parca/components';
18
23
  import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
19
24
 
20
25
  import {
@@ -25,19 +30,12 @@ import {
25
30
  FIELD_MAPPING_FILE,
26
31
  } from '../../ProfileFlameGraph/FlameGraphArrow';
27
32
  import {CurrentPathFrame} from '../../ProfileFlameGraph/FlameGraphArrow/utils';
28
- import {
29
- flamechartDimensionParser,
30
- groupByParser,
31
- jsonParser,
32
- stringParam,
33
- } from '../../hooks/urlParsers';
34
- import {useColorBy} from '../../hooks/useColorBy';
35
33
  import {useResetFlameGraphState} from './useResetFlameGraphState';
36
34
 
37
35
  export const useVisualizationState = (): {
38
36
  curPathArrow: CurrentPathFrame[];
39
37
  setCurPathArrow: (path: CurrentPathFrame[]) => void;
40
- colorStackLegend: string | null;
38
+ colorStackLegend: string | undefined;
41
39
  colorBy: string;
42
40
  setColorBy: (colorBy: string) => void;
43
41
  groupBy: string[];
@@ -46,52 +44,46 @@ export const useVisualizationState = (): {
46
44
  setGroupByLabels: (labels: string[]) => void;
47
45
  flamechartDimension: string[];
48
46
  setFlamechartDimension: (labels: string[]) => void;
49
- sandwichFunctionName: string | null;
50
- setSandwichFunctionName: (sandwichFunctionName: string | null) => void;
47
+ sandwichFunctionName: string | undefined;
48
+ setSandwichFunctionName: (sandwichFunctionName: string | undefined) => void;
51
49
  resetSandwichFunctionName: () => void;
52
50
  alignFunctionName: string;
53
51
  setAlignFunctionName: (align: string) => void;
54
52
  } => {
53
+ const [colorByPreference, setColorByPreference] = useUserPreference<string>(
54
+ USER_PREFERENCES.COLOR_BY.key
55
+ );
55
56
  const [alignFunctionNamePreference, setAlignFunctionNamePreference] = useUserPreference<string>(
56
57
  USER_PREFERENCES.ALIGN_FUNCTION_NAME.key
57
58
  );
58
59
 
59
- const [curPathArrow, setRawCurPathArrow] = useQueryState(
60
- 'cur_path',
61
- jsonParser<CurrentPathFrame[]>().withDefault([])
62
- );
63
- const setCurPathArrow = useCallback(
64
- (path: CurrentPathFrame[]) => {
65
- void setRawCurPathArrow(path);
66
- },
67
- [setRawCurPathArrow]
68
- );
69
- const [colorStackLegend] = useQueryState('color_stack_legend', stringParam);
70
- const {colorBy, setColorBy} = useColorBy();
71
- const [alignFunctionNameRaw, setStoreAlignFunctionName] = useQueryState(
72
- 'align_function_name',
73
- stringParam
74
- );
75
- const alignFunctionName = alignFunctionNameRaw ?? alignFunctionNamePreference ?? 'left';
76
- const [groupBy, setStoreGroupBy] = useQueryState(
77
- 'group_by',
78
- groupByParser.withDefault([FIELD_FUNCTION_NAME])
79
- );
80
- const [sandwichFunctionName, setRawSandwichFunctionName] = useQueryState(
81
- 'sandwich_function_name',
82
- stringParam
60
+ const [curPathArrow, setCurPathArrow] = useURLStateCustom<CurrentPathFrame[]>('cur_path', {
61
+ parse: JSONParser<CurrentPathFrame[]>,
62
+ stringify: JSONSerializer,
63
+ defaultValue: '[]',
64
+ });
65
+ const [colorStackLegend] = useURLState<string | undefined>('color_stack_legend');
66
+ const [colorBy, setStoreColorBy] = useURLState('color_by', {
67
+ defaultValue: colorByPreference,
68
+ });
69
+ const [alignFunctionName, setStoreAlignFunctionName] = useURLState('align_function_name', {
70
+ defaultValue: alignFunctionNamePreference,
71
+ });
72
+ const [groupBy, setStoreGroupBy] = useURLState<string[]>('group_by', {
73
+ defaultValue: [FIELD_FUNCTION_NAME],
74
+ alwaysReturnArray: true,
75
+ });
76
+ const [sandwichFunctionName, setSandwichFunctionName] = useURLState<string | undefined>(
77
+ 'sandwich_function_name'
83
78
  );
84
- const setSandwichFunctionName = useCallback(
85
- (name: string | null) => {
86
- void setRawSandwichFunctionName(name);
87
- },
88
- [setRawSandwichFunctionName]
89
- );
90
- const [flamechartDimension, setStoreFlamechartDimension] = useQueryState(
79
+ const [flamechartDimension, setStoreFlamechartDimension] = useURLState<string[]>(
91
80
  'flamechart_dimension',
92
- flamechartDimensionParser.withDefault([])
81
+ {
82
+ alwaysReturnArray: true,
83
+ }
93
84
  );
94
85
  const resetFlameGraphState = useResetFlameGraphState();
86
+ const batchUpdates = useURLStateBatch();
95
87
 
96
88
  const levelsOfProfiling = useMemo(
97
89
  () => [
@@ -105,46 +97,62 @@ export const useVisualizationState = (): {
105
97
 
106
98
  const setGroupBy = useCallback(
107
99
  (keys: string[]): void => {
108
- void setStoreGroupBy(keys);
100
+ setStoreGroupBy(keys);
109
101
  },
110
102
  [setStoreGroupBy]
111
103
  );
112
104
 
113
105
  const toggleGroupBy = useCallback(
114
106
  (key: string): void => {
115
- if (groupBy.includes(key)) {
116
- setGroupBy(groupBy.filter(v => v !== key));
117
- } else {
118
- const filteredGroupBy = groupBy.filter(item => !levelsOfProfiling.includes(item));
119
- setGroupBy([...filteredGroupBy, key]);
120
- }
121
- resetFlameGraphState();
107
+ // Batch updates to combine setGroupBy + resetFlameGraphState into single URL navigation
108
+ batchUpdates(() => {
109
+ if (groupBy.includes(key)) {
110
+ setGroupBy(groupBy.filter(v => v !== key)); // remove
111
+ } else {
112
+ const filteredGroupBy = groupBy.filter(item => !levelsOfProfiling.includes(item));
113
+ setGroupBy([...filteredGroupBy, key]); // add
114
+ }
115
+
116
+ resetFlameGraphState();
117
+ });
122
118
  },
123
- [groupBy, setGroupBy, levelsOfProfiling, resetFlameGraphState]
119
+ [groupBy, setGroupBy, levelsOfProfiling, resetFlameGraphState, batchUpdates]
124
120
  );
125
121
 
126
122
  const setGroupByLabels = useCallback(
127
123
  (labels: string[]): void => {
128
- setGroupBy(groupBy.filter(l => !l.startsWith(`${FIELD_LABELS}.`)).concat(labels));
129
- resetFlameGraphState();
124
+ // Batch updates to combine setGroupBy + resetFlameGraphState into single URL navigation
125
+ batchUpdates(() => {
126
+ setGroupBy(groupBy.filter(l => !l.startsWith(`${FIELD_LABELS}.`)).concat(labels));
127
+
128
+ resetFlameGraphState();
129
+ });
130
130
  },
131
- [groupBy, setGroupBy, resetFlameGraphState]
131
+ [groupBy, setGroupBy, resetFlameGraphState, batchUpdates]
132
132
  );
133
133
 
134
134
  const setFlamechartDimension = useCallback(
135
135
  (labels: string[]): void => {
136
- void setStoreFlamechartDimension(labels.filter(l => l.startsWith(`${FIELD_LABELS}.`)));
136
+ setStoreFlamechartDimension(labels.filter(l => l.startsWith(`${FIELD_LABELS}.`)));
137
137
  },
138
138
  [setStoreFlamechartDimension]
139
139
  );
140
140
 
141
141
  const resetSandwichFunctionName = useCallback((): void => {
142
- setSandwichFunctionName(null);
142
+ setSandwichFunctionName(undefined);
143
143
  }, [setSandwichFunctionName]);
144
144
 
145
+ const setColorBy = useCallback(
146
+ (value: string): void => {
147
+ setStoreColorBy(value);
148
+ setColorByPreference(value);
149
+ },
150
+ [setStoreColorBy, setColorByPreference]
151
+ );
152
+
145
153
  const setAlignFunctionName = useCallback(
146
154
  (value: string): void => {
147
- void setStoreAlignFunctionName(value);
155
+ setStoreAlignFunctionName(value);
148
156
  setAlignFunctionNamePreference(value);
149
157
  },
150
158
  [setStoreAlignFunctionName, setAlignFunctionNamePreference]
@@ -154,7 +162,7 @@ export const useVisualizationState = (): {
154
162
  curPathArrow,
155
163
  setCurPathArrow,
156
164
  colorStackLegend,
157
- colorBy,
165
+ colorBy: (colorBy as string) ?? '',
158
166
  setColorBy,
159
167
  groupBy,
160
168
  setGroupBy,
@@ -165,7 +173,7 @@ export const useVisualizationState = (): {
165
173
  sandwichFunctionName,
166
174
  setSandwichFunctionName,
167
175
  resetSandwichFunctionName,
168
- alignFunctionName,
176
+ alignFunctionName: (alignFunctionName as string) ?? 'left',
169
177
  setAlignFunctionName,
170
178
  };
171
179
  };
@@ -13,10 +13,15 @@
13
13
 
14
14
  import {useEffect, useMemo, useState} from 'react';
15
15
 
16
- import {useQueryState} from 'nuqs';
17
-
18
16
  import {QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
19
- import {useGrpcMetadata, useParcaContext} from '@parca/components';
17
+ import {
18
+ NumberParser,
19
+ NumberSerializer,
20
+ useGrpcMetadata,
21
+ useParcaContext,
22
+ useURLState,
23
+ useURLStateCustom,
24
+ } from '@parca/components';
20
25
  import {saveAsBlob} from '@parca/utilities';
21
26
 
22
27
  import {validateFlameChartQuery} from './ProfileFlameGraph';
@@ -30,14 +35,6 @@ import {MergedProfileSource, ProfileSource} from './ProfileSource';
30
35
  import {ProfileView} from './ProfileView';
31
36
  import {useProfileFilters} from './ProfileView/components/ProfileFilters/useProfileFilters';
32
37
  import type {SamplesSeries} from './ProfileView/types/visualization';
33
- import {
34
- flamechartDimensionParser,
35
- groupByParser,
36
- intParam,
37
- invertCallStackParser,
38
- stringParam,
39
- } from './hooks/urlParsers';
40
- import {useDashboardItems} from './hooks/useDashboardItems';
41
38
  import {useQuery} from './useQuery';
42
39
  import {downloadPprof} from './utils';
43
40
 
@@ -56,14 +53,22 @@ export const ProfileViewWithData = ({
56
53
  onSwitchToFifteenMinutes,
57
54
  }: ProfileViewWithDataProps): JSX.Element => {
58
55
  const metadata = useGrpcMetadata();
59
- const {dashboardItems, setDashboardItems} = useDashboardItems();
60
- const [sourceBuildID] = useQueryState('source_buildid', stringParam);
61
- const [sourceFilename] = useQueryState('source_filename', stringParam);
62
- const [groupBy] = useQueryState('group_by', groupByParser.withDefault([FIELD_FUNCTION_NAME]));
63
- const [sandwichFunctionName] = useQueryState('sandwich_function_name', stringParam);
64
- const [flamechartDimension] = useQueryState('flamechart_dimension', flamechartDimensionParser);
56
+ const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
57
+ alwaysReturnArray: true,
58
+ });
59
+ const [sourceBuildID] = useURLState<string>('source_buildid');
60
+ const [sourceFilename] = useURLState<string>('source_filename');
61
+ const [groupBy] = useURLState<string[]>('group_by', {
62
+ defaultValue: [FIELD_FUNCTION_NAME],
63
+ alwaysReturnArray: true,
64
+ });
65
+ const [sandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
66
+ const [flamechartDimension] = useURLState<string[]>('flamechart_dimension', {
67
+ alwaysReturnArray: true,
68
+ });
65
69
 
66
- const [invertCallStack] = useQueryState('invert_call_stack', invertCallStackParser);
70
+ const [invertStack] = useURLState('invert_call_stack');
71
+ const invertCallStack = invertStack === 'true';
67
72
 
68
73
  const [pprofDownloading, setPprofDownloading] = useState<boolean>(false);
69
74
 
@@ -105,7 +110,7 @@ export const ProfileViewWithData = ({
105
110
  skip: !dashboardItems.includes('flamegraph'),
106
111
  nodeTrimThreshold,
107
112
  groupBy,
108
- invertCallStack: invertCallStack ?? false,
113
+ invertCallStack,
109
114
  protoFilters,
110
115
  });
111
116
 
@@ -129,10 +134,11 @@ export const ProfileViewWithData = ({
129
134
  );
130
135
 
131
136
  // Samples step count: 2px per data point for finer granularity in strips
132
- const [samplesStepCount] = useQueryState(
133
- 'samples_step_count',
134
- intParam.withDefault(getStepCountFromScreenWidth(2))
135
- );
137
+ const [samplesStepCount] = useURLStateCustom<number>('samples_step_count', {
138
+ defaultValue: String(getStepCountFromScreenWidth(2)),
139
+ parse: NumberParser,
140
+ stringify: NumberSerializer,
141
+ });
136
142
 
137
143
  const {
138
144
  isLoading: samplesLoading,
@@ -197,8 +203,8 @@ export const ProfileViewWithData = ({
197
203
  error: sourceError,
198
204
  } = useQuery(queryClient, profileSource, QueryRequest_ReportType.SOURCE, {
199
205
  skip: !dashboardItems.includes('source'),
200
- sourceBuildID: sourceBuildID ?? undefined,
201
- sourceFilename: sourceFilename ?? undefined,
206
+ sourceBuildID,
207
+ sourceFilename,
202
208
  protoFilters,
203
209
  });
204
210
 
@@ -210,8 +216,8 @@ export const ProfileViewWithData = ({
210
216
  nodeTrimThreshold,
211
217
  groupBy: [FIELD_FUNCTION_NAME],
212
218
  invertCallStack: true,
213
- sandwichByFunction: sandwichFunctionName ?? undefined,
214
- skip: sandwichFunctionName == null && !dashboardItems.includes('sandwich'),
219
+ sandwichByFunction: sandwichFunctionName,
220
+ skip: sandwichFunctionName === undefined && !dashboardItems.includes('sandwich'),
215
221
  protoFilters,
216
222
  });
217
223
 
@@ -223,8 +229,8 @@ export const ProfileViewWithData = ({
223
229
  nodeTrimThreshold,
224
230
  groupBy: [FIELD_FUNCTION_NAME],
225
231
  invertCallStack: false,
226
- sandwichByFunction: sandwichFunctionName ?? undefined,
227
- skip: sandwichFunctionName == null && !dashboardItems.includes('sandwich'),
232
+ sandwichByFunction: sandwichFunctionName,
233
+ skip: sandwichFunctionName === undefined && !dashboardItems.includes('sandwich'),
228
234
  protoFilters,
229
235
  });
230
236
 
@@ -14,15 +14,14 @@
14
14
  import React, {useRef, useState} from 'react';
15
15
 
16
16
  import {AnimatePresence, motion} from 'framer-motion';
17
- import {useQueryState} from 'nuqs';
18
17
 
18
+ import {useURLState} from '@parca/components';
19
19
  import {TEST_IDS, testId} from '@parca/test-utils';
20
20
 
21
21
  import {ProfileSource} from '../ProfileSource';
22
22
  import {useDashboard} from '../ProfileView/context/DashboardContext';
23
23
  import {useVisualizationState} from '../ProfileView/hooks/useVisualizationState';
24
24
  import {SandwichData} from '../ProfileView/types/visualization';
25
- import {stringParam} from '../hooks/urlParsers';
26
25
  import {CalleesSection} from './components/CalleesSection';
27
26
  import {CallersSection} from './components/CallersSection';
28
27
 
@@ -36,7 +35,7 @@ const Sandwich = React.memo(function Sandwich({
36
35
  profileSource,
37
36
  }: Props): React.JSX.Element {
38
37
  const {dashboardItems} = useDashboard();
39
- const [sandwichFunctionName] = useQueryState('sandwich_function_name', stringParam);
38
+ const [sandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
40
39
 
41
40
  const callersRef = React.useRef<HTMLDivElement | null>(null);
42
41
  const calleesRef = React.useRef<HTMLDivElement | null>(null);
@@ -58,7 +57,7 @@ const Sandwich = React.memo(function Sandwich({
58
57
  transition={{duration: 0.5}}
59
58
  >
60
59
  <div className="relative flex flex-row">
61
- {sandwichFunctionName != null ? (
60
+ {sandwichFunctionName !== undefined ? (
62
61
  <div className="w-full flex flex-col" ref={callersCalleesContainerRef}>
63
62
  <CallersSection
64
63
  callersRef={callersRef}