Skip to content

Commit 661c4b1

Browse files
committed
Improve search performance
1 parent 79313b5 commit 661c4b1

3 files changed

Lines changed: 30 additions & 33 deletions

File tree

src/components/Docs/ContentList.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { SchemaEnumView } from "./SchemaEnum";
99
import { ClassTree } from "./ClassTree";
1010
import { Declaration } from "./api";
1111
import { DeclarationsContext, declarationKey } from "./DeclarationsContext";
12-
import { SearchContext } from "../Search/SearchContext";
1312
import { GAMES } from "../../games";
1413

1514
const CardBlock = styled.div`
@@ -176,7 +175,6 @@ function renderItem(declaration: Declaration) {
176175

177176
export function ContentList() {
178177
const { declarations, metadata, loading, error } = useContext(DeclarationsContext);
179-
const { search } = useContext(SearchContext);
180178
const { data, isSearching } = useFilteredData(declarations);
181179
const [showTree, setShowTree] = useState(
182180
() => /bot|crawl|spider|slurp/i.test(navigator.userAgent) || navigator.webdriver,
@@ -186,7 +184,7 @@ export function ContentList() {
186184
<ContentWrapper>
187185
{data.length > 0 ? (
188186
isSearching ? (
189-
<LazyList key={search} data={data} render={renderItem} />
187+
<LazyList data={data} render={renderItem} />
190188
) : (
191189
<ScrollableList data={data} render={renderItem} />
192190
)

src/components/Lists.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useRef } from "react";
2-
import { useVirtualizer, useWindowVirtualizer } from "@tanstack/react-virtual";
1+
import React, { useEffect, useRef } from "react";
2+
import { useWindowVirtualizer } from "@tanstack/react-virtual";
33

44
interface Props<T> {
55
data: T[];
@@ -13,7 +13,7 @@ export function LazyList<T>({ data, render }: Props<T>) {
1313
const virtualizer = useWindowVirtualizer({
1414
count: data.length,
1515
estimateSize: () => 80,
16-
overscan: 10,
16+
overscan: 2,
1717
scrollMargin: parentOffsetRef.current,
1818
});
1919

@@ -24,6 +24,15 @@ export function LazyList<T>({ data, render }: Props<T>) {
2424
}
2525
}, []);
2626

27+
// Scroll to top when search results change (skip initial mount)
28+
const prevDataRef = useRef(data);
29+
useEffect(() => {
30+
if (prevDataRef.current !== data) {
31+
prevDataRef.current = data;
32+
window.scrollTo(0, 0);
33+
}
34+
}, [data]);
35+
2736
return (
2837
<div ref={parentRef}>
2938
<div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>

src/components/Search/index.tsx

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
1+
import React, { useContext, useEffect, useRef, useState, useTransition } from "react";
22
import { useLocation, useNavigate } from "react-router-dom";
33
import { styled } from "@linaria/react";
44
import { SearchContext } from "./SearchContext";
@@ -60,7 +60,7 @@ export function SearchBox({
6060
const [inputValue, setInputValue] = useState(search);
6161
const navigate = useNavigate();
6262
const location = useLocation();
63-
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
63+
const [, startTransition] = useTransition();
6464
const ownNavigateRef = useRef(false);
6565

6666
// Sync from URL (back/forward navigation, clicking links)
@@ -70,37 +70,27 @@ export function SearchBox({
7070
return;
7171
}
7272
const urlSearch = new URLSearchParams(location.search).get("search") ?? "";
73-
setSearch(urlSearch);
7473
setInputValue(urlSearch);
75-
clearTimeout(timerRef.current);
74+
startTransition(() => setSearch(urlSearch));
7675
// Intentionally depends only on location.search — we sync *from* the URL.
7776
// search/setSearch are omitted because including them would create an
7877
// infinite loop: setSearch triggers a navigate which changes location.search.
7978
}, [location.search]); // eslint-disable-line react-hooks/exhaustive-deps
8079

81-
// Cleanup debounce timer on unmount
82-
useEffect(() => () => clearTimeout(timerRef.current), []);
83-
84-
const onChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>(
85-
({ target: { value } }) => {
86-
setInputValue(value);
87-
clearTimeout(timerRef.current);
88-
timerRef.current = setTimeout(() => {
89-
const wasSearching = search !== "";
90-
setSearch(value);
91-
ownNavigateRef.current = true;
92-
// Push a history entry when starting a new search so back button works.
93-
// Replace while typing to avoid polluting history with every keystroke.
94-
const replace = wasSearching || value === "";
95-
if (value === "") {
96-
navigate(baseUrl, { replace });
97-
} else {
98-
navigate(`${baseUrl}?search=${encodeURIComponent(value)}`, { replace });
99-
}
100-
}, 150);
101-
},
102-
[search, setSearch, navigate, baseUrl],
103-
);
80+
const onChange: React.ChangeEventHandler<HTMLInputElement> = ({ target: { value } }) => {
81+
const wasSearching = inputValue !== "";
82+
setInputValue(value);
83+
ownNavigateRef.current = true;
84+
// Push a history entry when starting a new search so back button works.
85+
// Replace while typing to avoid polluting history with every keystroke.
86+
const replace = wasSearching || value === "";
87+
startTransition(() => {
88+
setSearch(value);
89+
navigate(value === "" ? baseUrl : `${baseUrl}?search=${encodeURIComponent(value)}`, {
90+
replace,
91+
});
92+
});
93+
};
10494

10595
const ref = useCtrlFHook<HTMLInputElement>();
10696

0 commit comments

Comments
 (0)