perf(blog): derive search results with useMemo instead of useState+useEffect

Fixes #1246

Replace the useState/useEffect pattern that synced fuzzy search results
into local state on every search or searcher change with a single useMemo
that derives results directly during render.

Before:
  const [results, setResults] = useState(allBlogs);
  useEffect(() => {
    setResults(searcher.search(search));
  }, [search, searcher]);

After:
  const gridItems = useMemo(() => {
    const results = search.trim() ? searcher.search(search) : allBlogs;
    ...
  }, [search, searcher, allBlogs, featuredSlug]);

This removes an extra re-render per keystroke and eliminates the stale
intermediate state that occurred between the search input change and the
effect firing.
This commit is contained in:
yeranyang 2026-04-28 12:16:27 +08:00 committed by guangyang1206
parent 61f4d05cd1
commit 4845b96209

View file

@ -3,7 +3,7 @@
import { format } from "date-fns"; import { format } from "date-fns";
import FuzzySearch from "fuzzy-search"; import FuzzySearch from "fuzzy-search";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { Container } from "@/components/container"; import { Container } from "@/components/container";
import type { BlogEntry } from "./page"; import type { BlogEntry } from "./page";
@ -127,17 +127,13 @@ function MagazineSearchGrid({
[allBlogs] [allBlogs]
); );
const [results, setResults] = useState(allBlogs);
useEffect(() => {
setResults(searcher.search(search));
}, [search, searcher]);
const gridItems = useMemo(() => { const gridItems = useMemo(() => {
const results = search.trim() ? searcher.search(search) : allBlogs;
if (search.trim()) { if (search.trim()) {
return results; return results;
} }
return results.filter((b) => b.slug !== featuredSlug); return results.filter((b) => b.slug !== featuredSlug);
}, [results, search, featuredSlug]); }, [search, searcher, allBlogs, featuredSlug]);
return ( return (
<section aria-labelledby="archive-heading"> <section aria-labelledby="archive-heading">