Write useDebouncedValue(value, delay) and export it from useDebouncedValue.js. The tea shop's search box gets a new value on every key press, but should only search once the customer pauses. Your hook returns a copy of value that lags behind: it catches up once value has stayed the same for delay milliseconds.
On the first render it returns value straight away. After that, each change starts the wait again, so while the customer keeps typing it keeps returning the value from before they started. A value that was replaced before its wait ended is never returned. Renders that don't change value don't restart the wait.
Leave no timers behind: at most one is waiting at a time, and removing the component clears it.
function TeaSearch() {
const [query, setQuery] = useState("");
const search = useDebouncedValue(query, 300);
// Show query in the box, and search for search.
}
The Preview shows Demo.jsx, a search box that uses your hook.
useFakeTimers();
const { result } = renderHook(() => useDebouncedValue("g", 300));
expect(result.current).toBe("g");
const clock = useFakeTimers();
const { result, rerender } = renderHook(({ value }) => useDebouncedValue(value, 300), { initialProps: { value: "g" } });
rerender({ value: "gr" });
expect(result.current).toBe("g");
await clock.advance(299);
expect(result.current).toBe("g");
await clock.advance(1);
expect(result.current).toBe("gr");
const clock = useFakeTimers();
const { result, rerender } = renderHook(({ value }) => useDebouncedValue(value, 300), { initialProps: { value: "g" } });
rerender({ value: "gr" });
await clock.advance(200);
rerender({ value: "gre" });
await clock.advance(200);
expect(result.current).toBe("g");
await clock.advance(100);
expect(result.current).toBe("gre");
const clock = useFakeTimers();
const { result, rerender } = renderHook(({ value }) => useDebouncedValue(value, 300), { initialProps: { value: "g" } });
rerender({ value: "gr" });
await clock.advance(200);
rerender({ value: "gr" });
await clock.advance(100);
expect(result.current).toBe("gr");
const clock = useFakeTimers();
const { result, rerender } = renderHook(({ value }) => useDebouncedValue(value, 1000), { initialProps: { value: "g" } });
rerender({ value: "gr" });
await clock.advance(999);
expect(result.current).toBe("g");
await clock.advance(1);
expect(result.current).toBe("gr");
const clock = useFakeTimers();
const { rerender } = renderHook(({ value }) => useDebouncedValue(value, 300), { initialProps: { value: "g" } });
rerender({ value: "gr" });
await clock.advance(100);
rerender({ value: "gre" });
await clock.advance(100);
rerender({ value: "gree" });
expect(clock.pending()).toBe(1);
const clock = useFakeTimers();
const { rerender, unmount } = renderHook(({ value }) => useDebouncedValue(value, 300), { initialProps: { value: "g" } });
rerender({ value: "gr" });
unmount();
expect(clock.pending()).toBe(0);
function TeaSearch() {
const [query, setQuery] = React.useState("");
const search = useDebouncedValue(query, 300);
return (
<div>
<label>
Search teas <input value={query} onChange={(event) => setQuery(event.target.value)} />
</label>
<p>Searching for: {search}</p>
</div>
);
}
const clock = useFakeTimers();
render(<TeaSearch />);
await user.type(screen.getByLabelText("Search teas"), "oolong");
expect(screen.getByText("Searching for:")).toBeInTheDocument();
await clock.advance(300);
expect(screen.getByText("Searching for: oolong")).toBeInTheDocument();
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with useDebouncedValue. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.