From c291df4a087d6b5577c11f193cf303df2922ba62 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:21:37 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Memoize=20expensive=20compu?= =?UTF-8?q?tations=20in=20TreeNode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wraps children sorting in `React.useMemo` to prevent sorting large directories on every render - Wraps recursive file counting in `React.useMemo` to prevent walking large directory subtrees on every render - Documented performance learnings in `.jules/bolt.md` Co-authored-by: julesklord <801266+julesklord@users.noreply.github.com> --- .jules/bolt.md | 3 +++ src/components/TreeNode.jsx | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..10d76cf --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-07-01 - Optimizing React rendering with useMemo +**Learning:** Found an O(n) array sorting operation and deep recursive countFiles inside the body of a React Function Component (`TreeNode`) which is called many times for deeply nested trees. React components that are pure should memoize their expensive operations instead of doing them synchronously during each render pass. +**Action:** Always wrap `children` sorting and recursive tree traversal computations in `React.useMemo` to prevent deep performance degradation during re-renders. diff --git a/src/components/TreeNode.jsx b/src/components/TreeNode.jsx index e91a1d6..3186ed2 100644 --- a/src/components/TreeNode.jsx +++ b/src/components/TreeNode.jsx @@ -18,13 +18,14 @@ export const TreeNode = React.memo(function TreeNodeInner(props){ var node=props.node,selected=props.selected,onSelect=props.onSelect,expanded=props.expanded,toggle=props.toggle,filterFolder=props.filterFolder,activeFilter=props.activeFilter; var isOpen=expanded.has(node.path); var isFiltered=activeFilter===node.path; - var children=Object.values(node.children).sort(function(a,b){return a.name.localeCompare(b.name);}); + var children=React.useMemo(function(){return Object.values(node.children).sort(function(a,b){return a.name.localeCompare(b.name);});}, [node.children]); + var fileCount=React.useMemo(function(){return countFiles(node);}, [node]); return React.createElement('div',null, React.createElement('div',{className:'tree-folder'+(isFiltered?' filtered':''),onClick:function(){if(node.path==='')filterFolder(null);else filterFolder(node.path);}}, React.createElement('span',{className:'tree-toggle'+(isOpen?' open':''),onClick:function(e){e.stopPropagation();toggle(node.path);}},children.length>0||node.files.length>0?'▶':''), React.createElement(Icon,{name:isOpen?'folder-open':'folder',size:'m',className:'tree-entry-icon'}), React.createElement('span',{className:'tree-name'},node.name), - React.createElement('span',{className:'tree-count'},countFiles(node)) + React.createElement('span',{className:'tree-count'},fileCount) ), isOpen&&React.createElement('div',{className:'tree-children'}, children.map(function(c){return React.createElement(TreeNode,{key:c.path,node:c,selected:selected,onSelect:onSelect,expanded:expanded,toggle:toggle,filterFolder:filterFolder,activeFilter:activeFilter});}),