Skip to content

Commit ed8d24f

Browse files
authored
Run page performance improvements (#1072)
* lotsOfLogs task now outputs much larger logs * Removed tree view collapse/expand animation * JSDocs for useDebounce * WIP moving filtering into the state * Reworked the reducer to do the filtering
1 parent d0ef362 commit ed8d24f

File tree

7 files changed

+266
-158
lines changed

7 files changed

+266
-158
lines changed

apps/webapp/app/components/primitives/TreeView/TreeView.tsx

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { VirtualItem, Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
22
import { motion } from "framer-motion";
33
import { MutableRefObject, RefObject, useCallback, useEffect, useReducer, useRef } from "react";
4-
import { UnmountClosed } from "react-collapse";
54
import { cn } from "~/utils/cn";
65
import { NodeState, NodesState, reducer } from "./reducer";
7-
import { applyFilterToState, concreteStateFromInput, selectedIdFromState } from "./utils";
6+
import { concreteStateFromInput, selectedIdFromState } from "./utils";
87

98
export type TreeViewProps<TData> = {
109
tree: FlatTree<TData>;
@@ -104,23 +103,22 @@ export function TreeView<TData>({
104103
if (!node) return null;
105104
const state = nodes[node.id];
106105
if (!state) return null;
106+
if (!state.visible) return null;
107107
return (
108108
<div
109109
key={node.id}
110110
data-index={virtualItem.index}
111111
ref={virtualizer.measureElement}
112-
className="overflow-clip [&_.ReactCollapse--collapse]:transition-all"
112+
className="overflow-clip"
113113
{...getNodeProps(node.id)}
114114
>
115-
<UnmountClosed key={node.id} isOpened={state.visible}>
116-
{renderNode({
117-
node,
118-
state,
119-
index: virtualItem.index,
120-
virtualizer: virtualizer,
121-
virtualItem,
122-
})}
123-
</UnmountClosed>
115+
{renderNode({
116+
node,
117+
state,
118+
index: virtualItem.index,
119+
virtualizer: virtualizer,
120+
virtualItem,
121+
})}
124122
</div>
125123
);
126124
})}
@@ -130,19 +128,23 @@ export function TreeView<TData>({
130128
);
131129
}
132130

133-
type TreeStateHookProps<TData> = {
131+
export type Filter<TData, TFilterValue> = {
132+
value?: TFilterValue;
133+
fn: (value: TFilterValue, node: FlatTreeItem<TData>) => boolean;
134+
};
135+
136+
type TreeStateHookProps<TData, TFilterValue> = {
134137
tree: FlatTree<TData>;
135138
selectedId?: string;
136139
collapsedIds?: string[];
137140
onSelectedIdChanged?: (selectedId: string | undefined) => void;
138-
onCollapsedIdsChanged?: (collapsedIds: string[]) => void;
139141
estimatedRowHeight: (params: {
140142
node: FlatTreeItem<TData>;
141143
state: NodeState;
142144
index: number;
143145
}) => number;
144146
parentRef: RefObject<any>;
145-
filter?: (node: FlatTreeItem<TData>) => boolean;
147+
filter?: Filter<TData, TFilterValue>;
146148
};
147149

148150
//this is so Framer Motion can be used to render the components
@@ -178,24 +180,24 @@ export type UseTreeStateOutput = {
178180
scrollToNode: (id: string) => void;
179181
};
180182

181-
export function useTree<TData>({
183+
export function useTree<TData, TFilterValue>({
182184
tree,
183185
selectedId,
184186
collapsedIds,
185187
onSelectedIdChanged,
186-
onCollapsedIdsChanged,
187188
parentRef,
188189
estimatedRowHeight,
189190
filter,
190-
}: TreeStateHookProps<TData>): UseTreeStateOutput {
191+
}: TreeStateHookProps<TData, TFilterValue>): UseTreeStateOutput {
191192
const previousNodeCount = useRef(tree.length);
192193
const previousSelectedId = useRef<string | undefined>(selectedId);
193194

194195
const [state, dispatch] = useReducer(
195196
reducer,
196-
concreteStateFromInput({ tree, selectedId, collapsedIds })
197+
concreteStateFromInput({ tree, selectedId, collapsedIds, filter })
197198
);
198199

200+
//fire onSelectedIdChanged()
199201
useEffect(() => {
200202
const selectedId = selectedIdFromState(state.nodes);
201203
if (selectedId !== previousSelectedId.current) {
@@ -204,22 +206,33 @@ export function useTree<TData>({
204206
}
205207
}, [state.changes.selectedId]);
206208

207-
useEffect(() => {
208-
if (state.changes.collapsedIds) {
209-
onCollapsedIdsChanged?.(state.changes.collapsedIds);
210-
}
211-
}, [state.changes.collapsedIds]);
212-
209+
//update tree when the number of nodes changes
213210
useEffect(() => {
214211
if (tree.length !== previousNodeCount.current) {
215212
previousNodeCount.current = tree.length;
216213
dispatch({ type: "UPDATE_TREE", payload: { tree } });
217214
}
218215
}, [previousNodeCount.current, tree.length]);
219216

217+
//update the filter, if it's changed
218+
const previousFilter = useRef(filter);
219+
useEffect(() => {
220+
//check if the value (not reference) of the filter is the same
221+
const previousValue = previousFilter.current
222+
? JSON.stringify(previousFilter.current.value)
223+
: undefined;
224+
const newValue = filter ? JSON.stringify(filter.value) : undefined;
225+
226+
previousFilter.current = filter;
227+
228+
if (previousValue !== newValue) {
229+
dispatch({ type: "UPDATE_FILTER", payload: { filter } });
230+
}
231+
}, [filter?.value]);
232+
220233
const virtualizer = useVirtualizer({
221-
count: tree.length,
222-
getItemKey: (index) => tree[index].id,
234+
count: state.visibleNodeIds.length,
235+
getItemKey: (index) => state.visibleNodeIds[index],
223236
getScrollElement: () => parentRef.current,
224237
estimateSize: (index: number) => {
225238
return estimatedRowHeight({
@@ -269,21 +282,21 @@ export function useTree<TData>({
269282

270283
const expandNode = useCallback(
271284
(id: string, scrollToNode = true) => {
272-
dispatch({ type: "EXPAND_NODE", payload: { id, tree, scrollToNode, scrollToNodeFn } });
285+
dispatch({ type: "EXPAND_NODE", payload: { id, scrollToNode, scrollToNodeFn } });
273286
},
274287
[state]
275288
);
276289

277290
const collapseNode = useCallback(
278291
(id: string) => {
279-
dispatch({ type: "COLLAPSE_NODE", payload: { id, tree } });
292+
dispatch({ type: "COLLAPSE_NODE", payload: { id } });
280293
},
281294
[state]
282295
);
283296

284297
const toggleExpandNode = useCallback(
285298
(id: string, scrollToNode = true) => {
286-
dispatch({ type: "TOGGLE_EXPAND_NODE", payload: { id, tree, scrollToNode, scrollToNodeFn } });
299+
dispatch({ type: "TOGGLE_EXPAND_NODE", payload: { id, scrollToNode, scrollToNodeFn } });
287300
},
288301
[state]
289302
);
@@ -292,7 +305,7 @@ export function useTree<TData>({
292305
(scrollToNode = true) => {
293306
dispatch({
294307
type: "SELECT_FIRST_VISIBLE_NODE",
295-
payload: { tree, scrollToNode, scrollToNodeFn },
308+
payload: { scrollToNode, scrollToNodeFn },
296309
});
297310
},
298311
[tree, state]
@@ -302,7 +315,7 @@ export function useTree<TData>({
302315
(scrollToNode = true) => {
303316
dispatch({
304317
type: "SELECT_LAST_VISIBLE_NODE",
305-
payload: { tree, scrollToNode, scrollToNodeFn },
318+
payload: { scrollToNode, scrollToNodeFn },
306319
});
307320
},
308321
[tree, state]
@@ -312,7 +325,7 @@ export function useTree<TData>({
312325
(scrollToNode = true) => {
313326
dispatch({
314327
type: "SELECT_NEXT_VISIBLE_NODE",
315-
payload: { tree, scrollToNode, scrollToNodeFn },
328+
payload: { scrollToNode, scrollToNodeFn },
316329
});
317330
},
318331
[state]
@@ -322,7 +335,7 @@ export function useTree<TData>({
322335
(scrollToNode = true) => {
323336
dispatch({
324337
type: "SELECT_PREVIOUS_VISIBLE_NODE",
325-
payload: { tree, scrollToNode, scrollToNodeFn },
338+
payload: { scrollToNode, scrollToNodeFn },
326339
});
327340
},
328341
[state]
@@ -332,43 +345,43 @@ export function useTree<TData>({
332345
(scrollToNode = true) => {
333346
dispatch({
334347
type: "SELECT_PARENT_NODE",
335-
payload: { tree, scrollToNode, scrollToNodeFn },
348+
payload: { scrollToNode, scrollToNodeFn },
336349
});
337350
},
338351
[state]
339352
);
340353

341354
const expandAllBelowDepth = useCallback(
342355
(depth: number) => {
343-
dispatch({ type: "EXPAND_ALL_BELOW_DEPTH", payload: { tree, depth } });
356+
dispatch({ type: "EXPAND_ALL_BELOW_DEPTH", payload: { depth } });
344357
},
345358
[state]
346359
);
347360

348361
const collapseAllBelowDepth = useCallback(
349362
(depth: number) => {
350-
dispatch({ type: "COLLAPSE_ALL_BELOW_DEPTH", payload: { tree, depth } });
363+
dispatch({ type: "COLLAPSE_ALL_BELOW_DEPTH", payload: { depth } });
351364
},
352365
[state]
353366
);
354367

355368
const expandLevel = useCallback(
356369
(level: number) => {
357-
dispatch({ type: "EXPAND_LEVEL", payload: { tree, level } });
370+
dispatch({ type: "EXPAND_LEVEL", payload: { level } });
358371
},
359372
[state]
360373
);
361374

362375
const collapseLevel = useCallback(
363376
(level: number) => {
364-
dispatch({ type: "COLLAPSE_LEVEL", payload: { tree, level } });
377+
dispatch({ type: "COLLAPSE_LEVEL", payload: { level } });
365378
},
366379
[state]
367380
);
368381

369382
const toggleExpandLevel = useCallback(
370383
(level: number) => {
371-
dispatch({ type: "TOGGLE_EXPAND_LEVEL", payload: { tree, level } });
384+
dispatch({ type: "TOGGLE_EXPAND_LEVEL", payload: { level } });
372385
},
373386
[state]
374387
);
@@ -480,7 +493,7 @@ export function useTree<TData>({
480493

481494
return {
482495
selected: selectedIdFromState(state.nodes),
483-
nodes: filter ? applyFilterToState(tree, state.nodes, filter) : state.nodes,
496+
nodes: state.nodes,
484497
getTreeProps,
485498
getNodeProps,
486499
selectNode,

0 commit comments

Comments
 (0)