What is it good for?
function getWindowDimensions() { const { innerWidth: width, innerHeight: height } = window; return { width, height }; } export function MyComponent() { const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); const handleResize = useCallback(() => { setWindowDimensions(getWindowDimensions()); }, []); useEffect(() => { handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [handleResize]); /* ... */
function getWindowDimensions() { const { innerWidth: width, innerHeight: height } = window; return { width, height }; } export function MyComponent() { const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); const handleResize = useCallback(() => { setWindowDimensions(getWindowDimensions()); }, []); useEffect(() => { handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [handleResize]); /* ... */
function getWindowDimensions() { const { innerWidth: width, innerHeight: height } = window; return { width, height }; } export function MyComponent() { const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); const handleResize = useCallback(() => { setWindowDimensions(getWindowDimensions()); }, []); useEffect(() => { handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [handleResize]); /* ... */
function getWindowDimensions() { const { innerWidth: width, innerHeight: height } = window; return { width, height }; } export function MyComponent() { const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); const handleResize = useCallback(() => { setWindowDimensions(getWindowDimensions()); }, []); useEffect(() => { handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [handleResize]); /* ... */
Add a search input and use useEffect to implement a debounce on the pokemon list as a filter.
Add a search input and use useEffect to implement a debounce on the pokemon list as a filter.
Use setTimeout & clearTimeout to build a delay
const timeoutId = setTimeout( () => console.log('execute with timeout'), 300); clearTimeout(timeoutId);
const timeoutId = setTimeout( () => console.log('execute with timeout'), 300); clearTimeout(timeoutId);
const timeoutId = setTimeout( () => console.log('execute with timeout'), 300); clearTimeout(timeoutId);
const timeoutId = setTimeout( () => console.log('execute with timeout'), 300); clearTimeout(timeoutId);
SearchPanel.tsx
type Props = { searchTerm: string; onSearchChanged: (searchTerm: string) => void; }; function SearchPanel({ searchTerm, onSearchChanged }: Props) { return ( <div > <label htmlFor="searchTerm">Search</label> <input type="text" id="searchTerm" value={searchTerm} placeholder="Pokemon name" onChange={(e) => { onSearchChanged(e.target.value); }} /> </div> ); } export { SearchPanel };
ListPage.tsx
function ListPage() { const [searchTerm, setSearchTerm] = useState(""); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); useEffect(() => { const timeoutId = setTimeout(() => setDebouncedSearchTerm(searchTerm), 300); return () => clearTimeout(timeoutId); }, [searchTerm]); /* ... */ }
function ListPage() { const [searchTerm, setSearchTerm] = useState(""); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); useEffect(() => { const timeoutId = setTimeout(() => setDebouncedSearchTerm(searchTerm), 300); return () => clearTimeout(timeoutId); }, [searchTerm]); /* ... */ }
ListPage.tsx
function ListPage() { /* ... */ return ( <> <SearchPanel searchTerm={searchTerm} onSearchChanged={setSearchTerm} /> {pokemons ? ( pokemons ?.filter((p) => p.name.includes(debouncedSearchTerm)) .map((pokemon) => ( <PokeListEntry key={pokemon.name} name={pokemon.name} /> )) ) : ( <div>LOADING</div> )} </> }
function ListPage() { /* ... */ return ( <> <SearchPanel searchTerm={searchTerm} onSearchChanged={setSearchTerm} /> {pokemons ? ( pokemons ?.filter((p) => p.name.includes(debouncedSearchTerm)) .map((pokemon) => ( <PokeListEntry key={pokemon.name} name={pokemon.name} /> )) ) : ( <div>LOADING</div> )} </> }
We can reuse a concrete Hook logic by extracting it into a custom Hook .
function MyComponent() { const result = /* logic that uses Hooks */ return <p>{result}</p>; }
The logic to be extracted can use a single or multiple Hooks.
We simply move the logic into its own function.
function useMyCustomHook() { /* logic that uses Hooks */ return result; } function MyComponent() { const result = useMyCustomHook(); return <p>{result}</p>; }
function useMyCustomHook() { /* logic that uses Hooks */ return result; } function MyComponent() { const result = useMyCustomHook(); return <p>{result}</p>; }
function useMyCustomHook() { /* logic that uses Hooks */ return result; } function MyComponent() { const result = useMyCustomHook(); return <p>{result}</p>; }
In the component we just call this function.
Stick to the Hooks naming convention: use*
Extract the debounce logic for the search into a custom Hook.
useDebouncedSearch.ts
import { useEffect, useState } from "react"; function useDebouncedSearch(initialSearchTerm: string) { const [searchTerm, setSearchTerm] = useState(initialSearchTerm); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); useEffect(() => { const timoutId = setTimeout(() => setDebouncedSearchTerm(searchTerm), 300); return () => clearTimeout(timoutId); }, [searchTerm]); return { searchTerm, setSearchTerm, debouncedSearchTerm }; } export { useDebouncedSearch };
import { useEffect, useState } from "react"; function useDebouncedSearch(initialSearchTerm: string) { const [searchTerm, setSearchTerm] = useState(initialSearchTerm); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); useEffect(() => { const timoutId = setTimeout(() => setDebouncedSearchTerm(searchTerm), 300); return () => clearTimeout(timoutId); }, [searchTerm]); return { searchTerm, setSearchTerm, debouncedSearchTerm }; } export { useDebouncedSearch };
List.tsx
function ListPage() { const { searchTerm, setSearchTerm, debouncedSearchTerm } = useDebouncedSearch(""); const pokemons = usePokeList(); /* … */ } export { ListPage };
We learned…