# useLoading()

Helps track loading and error state of imperative async functions.

> **Tip**
>
> [useSuspense()](https://dataclient.io/vue/api/useSuspense.md) or [useDLE()](https://dataclient.io/vue/api/useDLE.md) are better for GET/read endpoints.

## Usage

Files with a `route` are pages mounted at that path, with its params as props (vue-router `props: true`).

```ts title="PostResource"
import { Entity, resource } from '@data-client/rest';

export class Post extends Entity {
  id = 0;
  author = 0;
  title = '';
  body = '';
  votes = 0;

  static key = 'Post';

  get img() {
    return `https://picsum.photos/seed/${this.id % 16}/96/72`;
  }
}
export const PostResource = resource({
  path: '/posts/:id',
  schema: Post,
});
```

```html title="PostDetail.vue" route="/posts/:id"
<script setup lang="ts">
  import { computed } from 'vue';
  import { useSuspense } from '@data-client/vue';
  import { PostResource } from './PostResource';

  const props = defineProps<{ id: string }>();
  const post = await useSuspense(PostResource.get, computed(() => ({
    id: props.id,
  })));
</script>

<template>
  <div>
    <div class="voteBlock">
      <img :src="post.img" width="70" height="52" />
    </div>
    <div>
      <h4>{{ post.title }}</h4>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>
```

```html title="PostForm.vue"
<script setup lang="ts">
  defineProps<{
    loading: boolean;
    error: Error | undefined;
  }>();
  const emit = defineEmits<{ submit: [data: FormData] }>();

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    emit('submit', new FormData(e.target as HTMLFormElement));
  };
</script>

<template>
  <form @submit="handleSubmit" :aria-busy="loading">
    <fieldset :disabled="loading">
      <TextInput
        label="Title"
        name="title"
        defaultValue="My New Post"
        required
      />
      <TextArea name="body" :rows="12" label="Body" required>
        After clicking 'save', the form is locked and the button animates
        until the POST completes. Upon completion the newly
        created post is displayed immediately as Reactive Data Client is
        able to use the fetch response to populate the store.
      </TextArea>
      <div v-if="error" class="alert alert--danger">{{ error.message }}</div>
      <button type="submit">{{ loading ? 'Saving…' : 'Save' }}</button>
    </fieldset>
  </form>
</template>
```

```html title="PostCreate.vue" route="/"
<script setup lang="ts">
  import { useLoading, useController } from '@data-client/vue';
  import { useRouter } from 'vue-router';
  import { PostResource } from './PostResource';
  import PostForm from './PostForm.vue';

  const ctrl = useController();
  const router = useRouter();
  const [handleSubmit, loading, error] = useLoading(
    async (data: FormData) => {
      const post = await ctrl.fetch(PostResource.getList.push, data);
      router.push(`/posts/${post.id}`);
    },
  );
</script>

<template>
  <PostForm @submit="handleSubmit" :loading="loading" :error="error" />
</template>
```

Returns the wrapped function along with `loading` and `error` [refs](https://vuejs.org/api/reactivity-core.html#ref).
The wrapped function is stable, so no dependency list is needed: any refs or props it reads are
read at call time.

## Types

```typescript
export default function useLoading<
  F extends (...args: any) => Promise<any>,
>(func: F): [F, Ref<boolean>, Ref<Error | undefined>];
```

`loading` is `true` while the returned promise is pending. If `func` rejects, the rejection is
caught and stored in `error`; it is cleared again on the next call.

## Examples

### Todo creation

Example app: [vue-todo-app](https://github.com/reactive/data-client/tree/master/examples/vue-todo-app) ([`src/components/TodoList.vue`](https://github.com/reactive/data-client/blob/master/examples/vue-todo-app/src/components/TodoList.vue), [`src/resources/TodoResource.ts`](https://github.com/reactive/data-client/blob/master/examples/vue-todo-app/src/resources/TodoResource.ts))
