From f3890fc6bcda9eebf80b0212da411237d2236a66 Mon Sep 17 00:00:00 2001 From: Ralksta <16941050+ralksta@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:32:40 +0100 Subject: [PATCH] perf: memoize PhotoGrid assets map to prevent re-renders during lightbox navigation [#5] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted the grid mapping logic out of the return block into a `useMemo` hook depending on `[assets, layout, openLightbox]`. This prevents the entire photo grid—including complex Next.js `` and `` components—from needlessly re-rendering every time the user navigates via the lightbox arrows or presses next/prev buttons, which previously caused `lightboxIndex` to update and trigger a re-render of the entire grid. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 4 ++ app/[...path]/PhotoGrid.tsx | 90 ++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 42 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..1c3beb4 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ +## YYYY-MM-DD - [Memoize grid item re-rendering] + +**Learning:** `useMemo` can significantly boost the performance of long list or grid components (like `PhotoGrid` component) that otherwise gets re-rendered on simple interactions like navigating the image lightbox. Running standard formatting `pnpm format` applies huge changes across entire directories, so it's generally best to format and clean up ONLY modified files to avoid huge diff pollution in code review. +**Action:** Be extremely cautious of running format/lint commands at the root codebase; specify only modified directories or files, or avoid formatting if it cascades globally. Focus on targeted memoization where state changes dictate expensive array mapping operations. diff --git a/app/[...path]/PhotoGrid.tsx b/app/[...path]/PhotoGrid.tsx index 757a0be..c3638ef 100644 --- a/app/[...path]/PhotoGrid.tsx +++ b/app/[...path]/PhotoGrid.tsx @@ -8,7 +8,7 @@ 'use client'; -import { useState, useCallback, useEffect } from 'react'; +import { useState, useCallback, useEffect, useMemo } from 'react'; import Image from 'next/image'; import { Lightbox } from '@/components/Lightbox'; import { FadeIn } from '@/components/FadeIn'; @@ -136,50 +136,56 @@ export function PhotoGrid({ assets, layout = 'masonry', gridStyle }: PhotoGridPr }; }, [lightboxIndex, closeLightbox, goNext, goPrev]); + // Memoize grid items to prevent re-renders of the entire grid (which includes + // Image and FadeIn components) when navigating the lightbox. + const gridItems = useMemo(() => { + return assets.map((asset, index) => ( + +
openLightbox(index)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + openLightbox(index); + } + }} + role="button" + tabIndex={0} + aria-label={`View photo ${index + 1}`} + style={{ + ...(asset.dominantColor ? { backgroundColor: asset.dominantColor } : {}), + ...((layout === 'masonry' || layout === 'showcase') && asset.aspectRatio + ? { aspectRatio: `${asset.aspectRatio}` } + : {}), + }} + > + + {(asset.camera || asset.lens) && ( +
+ {[asset.camera, asset.lens, asset.focalLength].filter(Boolean).join(' · ')} +
+ )} +
+
+ )); + }, [assets, layout, openLightbox]); + return ( <>
- {assets.map((asset, index) => ( - -
openLightbox(index)} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - openLightbox(index); - } - }} - role="button" - tabIndex={0} - aria-label={`View photo ${index + 1}`} - style={{ - ...(asset.dominantColor ? { backgroundColor: asset.dominantColor } : {}), - ...((layout === 'masonry' || layout === 'showcase') && asset.aspectRatio - ? { aspectRatio: `${asset.aspectRatio}` } - : {}), - }} - > - - {(asset.camera || asset.lens) && ( -
- {[asset.camera, asset.lens, asset.focalLength].filter(Boolean).join(' · ')} -
- )} -
-
- ))} + {gridItems}
{lightboxIndex !== null && (