diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..ad26003 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Zustand store destructuring causes excessive re-renders +**Learning:** Using `const { a, b } = useStore()` with Zustand subscribes the component to the entire store, causing it to re-render whenever *any* part of the store changes. This is exceptionally problematic in a canvas editor where state (like x/y coordinates of an element during drag) updates continuously. +**Action:** Always use selector functions `const a = useStore(state => state.a)` when accessing Zustand state to ensure the component only re-renders when the specific state it relies on changes. diff --git a/frontend/src/components/CanvasArea.tsx b/frontend/src/components/CanvasArea.tsx index ae81602..377b839 100644 --- a/frontend/src/components/CanvasArea.tsx +++ b/frontend/src/components/CanvasArea.tsx @@ -74,7 +74,15 @@ const ElementWrapper: React.FC = ({ element, isSelected, on }; export const CanvasArea = () => { - const { pages, activePageId, selectedElementId, selectElement, updateElement } = useEditorStore(); + // ⚡ Bolt Performance Optimization: + // Using selector functions instead of destructuring to prevent unnecessary + // re-renders when other unrelated parts of the Zustand store change. + // This is especially critical in CanvasArea to avoid whole-canvas re-renders during dragging. + const pages = useEditorStore((state) => state.pages); + const activePageId = useEditorStore((state) => state.activePageId); + const selectedElementId = useEditorStore((state) => state.selectedElementId); + const selectElement = useEditorStore((state) => state.selectElement); + const updateElement = useEditorStore((state) => state.updateElement); const activePage = pages.find(p => p.id === activePageId) || pages[0]; const [scale] = useState(1); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 8ec7193..d2af24d 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,7 +1,14 @@ import { useEditorStore } from '../store/useEditorStore'; export const Sidebar = () => { - const { pages, activePageId, addPage, setActivePage, selectedElementId } = useEditorStore(); + // ⚡ Bolt Performance Optimization: + // Using selector functions instead of destructuring to prevent unnecessary + // re-renders when other unrelated parts of the Zustand store change. + const pages = useEditorStore((state) => state.pages); + const activePageId = useEditorStore((state) => state.activePageId); + const addPage = useEditorStore((state) => state.addPage); + const setActivePage = useEditorStore((state) => state.setActivePage); + const selectedElementId = useEditorStore((state) => state.selectedElementId); return (
diff --git a/frontend/src/components/Toolbar.tsx b/frontend/src/components/Toolbar.tsx index efd1490..54a12f1 100644 --- a/frontend/src/components/Toolbar.tsx +++ b/frontend/src/components/Toolbar.tsx @@ -3,7 +3,11 @@ import { useEditorStore } from '../store/useEditorStore'; import type { TextElement } from '../types'; export const Toolbar = () => { - const { addElement, activePageId } = useEditorStore(); + // ⚡ Bolt Performance Optimization: + // Using selector functions instead of destructuring to prevent unnecessary + // re-renders when other unrelated parts of the Zustand store change. + const addElement = useEditorStore((state) => state.addElement); + const activePageId = useEditorStore((state) => state.activePageId); const handleAddText = () => { if (!activePageId) return;