DataInteractivityEvents
useDebounce
Creates a debounced version of an input value, only updating after a specified delay of inactivity. Also provides a loading state for use while the value updates.
Parameters
Name | Type | Description |
---|---|---|
initialValue | any | The initial value to be debounced. |
delay | number | The time (in milliseconds) to wait after the last change before updating the debounced value. |
Return Values
Name | Type | Description |
---|---|---|
value | any | The debounced version of the input value. |
update | function | Function to manually update the debounced value |
loading | boolean | Indicates whether the debounced value is in the process of updating. |
Source Code
export const useDebounce = (initialValue, delay = 250) => { let timeout = $state(null); let value = $state(initialValue); let loading = $state(false); const update = (newValue) => { if (timeout) clearTimeout(timeout); loading = true; timeout = setTimeout(() => { value = newValue; loading = false; }, delay); }; return { get value() { return value; }, update, get loading() { return loading; }, }; };
Fetching pokemon...
<script lang="ts"> import { onMount } from 'svelte'; import { useDebounce } from './useDebounce.svelte'; type Pokemon = { name: string; url: string }; const debounce = useDebounce('', 500); let pokemon = $state<Pokemon[]>(); const filterPokemonBySearch = (query: string) => { if (pokemon === undefined) return null; if (query.length < 3) return pokemon; return pokemon.filter(({ name }) => name.toLowerCase().includes(query.toLowerCase())); }; let filteredPokemon = $derived.by(() => filterPokemonBySearch(debounce.value)); const handleInput = (e: KeyboardEvent & { currentTarget: EventTarget & HTMLInputElement }) => { debounce.update(e.currentTarget.value); }; const fetchAllFirstGenerationPokemon = async () => { const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151'); const data = await res.json(); pokemon = data.results; }; onMount(fetchAllFirstGenerationPokemon); const getPokemonImageUrl = (url: string) => { const id = url.split('/').filter(Boolean).pop(); return `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`; }; </script> <div class="container"> <div class="header"> <input placeholder="Search pokemon..." on:keyup={handleInput} /> </div> <div class="results custom-scrollbar"> {#if !filteredPokemon || debounce.loading} <div class="spinner-container"> <div class="spinner" /> <span>Fetching pokemon...</span> </div> {:else if filteredPokemon.length === 0} <div class="empty-container"> <span class="emoji">😢</span> <span class="text">No pokemon found! (Other generations supported Q4 2027)</span> </div> {:else} {#each filteredPokemon as { name, url } (name)} {@const src = getPokemonImageUrl(url)} <div class="card"> <img {src} alt="Pokemon: {name}" /> <span>{name}</span> </div> {/each} {/if} </div> </div> <style lang="scss"> .container { display: flex; flex-direction: column; gap: 3rem; } .header { display: flex; flex-direction: column; align-items: center; } .header input { text-align: center; width: 300px; max-width: 100%; } .results { display: grid; grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); gap: 20px; max-height: 500px; overflow-y: auto; } .spinner-container { grid-column: 1 / -1; display: flex; flex-direction: column; align-items: center; gap: 1rem; padding: 6rem 0; } .spinner-container span { font-size: 14px; color: #c9c9c9; font-style: italic; } .empty-container { grid-column: 1 / -1; padding: 5.4rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; text-align: center; } .empty-container .emoji { font-size: 32px; } .empty-container .text { font-size: 14px; color: #c9c9c9; } .card { background-color: rgba(255, 255, 255, 0.03); border: 2px solid rgba(255, 255, 255, 0.03); border-radius: 10px; display: flex; flex-direction: column; align-items: center; gap: 1rem; padding: 1.5rem; } .card img { width: 72px; height: 72px; filter: drop-shadow(0 0 20px rgba(255, 128, 236, 0.2)); } .card span { font-weight: 700; font-size: 18px; } </style>