# useDebounce()

Delays updating the parameters by [debouncing](https://css-tricks.com/debouncing-throttling-explained-examples/).

Useful to avoid spamming network requests when parameters might change quickly (like a typeahead field).

> **Tip**
>
> `useDebounce()` returns [refs](https://vuejs.org/api/reactivity-core.html#ref), so the debounced
> value can be passed directly to other composables or components. `isPending` is true from the moment
> the input changes until the debounced value is updated.

## Usage

```ts title="IssueQuery"
import { RestEndpoint, Entity, Collection } from '@data-client/rest';
import { Temporal } from 'temporal-polyfill';

export class Issue extends Entity {
  number = 0;
  repository_url = '';
  labels_url = '';
  html_url = '';
  body = '';
  title = '';
  state: 'open' | 'closed' = 'open';
  locked = false;
  comments = 0;
  created_at = Temporal.Instant.fromEpochMilliseconds(0);
  updated_at = Temporal.Instant.fromEpochMilliseconds(0);
  closed_at: Temporal.Instant | null = null;
  authorAssociation = 'NONE';
  pullRequest: Record<string, any> | null = null;
  declare draft?: boolean;

  static schema = {
    created_at: Temporal.Instant.from,
    updated_at: Temporal.Instant.from,
    closed_at: Temporal.Instant.from,
  };

  pk() {
    return [this.repository_url, this.number].join(',');
  }
}

export const issueQuery = new RestEndpoint({
  urlPrefix: 'https://api.github.com',
  path: '/search/issues',
  searchParams: {} as { q: string },
  paginationField: 'page',
  schema: {
    incomplete_results: false,
    items: new Collection([Issue]),
    total_count: 0,
  },
});
```

```html title="IssueList.vue"
<script setup lang="ts">
  import { computed } from 'vue';
  import { useSuspense } from '@data-client/vue';
  import { issueQuery } from './IssueQuery';

  const props = defineProps<{
    query: string;
    owner: string;
    repo: string;
  }>();
  const response = await useSuspense(issueQuery, computed(() => ({
    q: `${props.query} repo:${props.owner}/${props.repo}`,
  })));
</script>

<template>
  <small style="display: block">{{ response.total_count }} results</small>
  <div v-for="issue in response.items.slice(0, 5)" :key="issue.pk()">
    <a :href="issue.html_url" target="_blank">{{ issue.title }}</a>
  </div>
</template>
```

```html title="SearchIssues.vue"
<script setup lang="ts">
  import { ref } from 'vue';
  import { useDebounce } from '@data-client/vue';
  import IssueList from './IssueList.vue';

  const query = ref('');
  const [debouncedQuery, isPending] = useDebounce(query, 200);
</script>

<template>
  <TextInput
    spellcheck="false"
    placeholder="Search react issues"
    v-model="query"
    :loading="isPending"
    autofocus
    size="large"
  >
    <SearchIcon />
  </TextInput>
  <Suspense>
    <IssueList :query="debouncedQuery" owner="facebook" repo="react" />
    <template #fallback><Loading /></template>
  </Suspense>
</template>
```

## Types

```typescript
function useDebounce<T>(
  value: T | Ref<T>,
  delay: number,
  updatable?: boolean | Ref<boolean>,
): [Ref<T>, Ref<boolean>];
```

`value` and `updatable` can be plain values or [refs](https://vuejs.org/api/reactivity-core.html#ref).
Returns a tuple of `[debouncedValue, isPending]` refs. When `updatable` is `false`, the debounced
value stops updating and `isPending` resets to `false`.
