Pular para o conteúdo principal

useDLE() - [D]ata, [L]oading e [E]rror (dados, carregamento e erro)

Renderização de dados assíncronos de alto desempenho, sem overfetching. Com metadados do fetch.

Caso você não possa usar suspense, useDLE() é igual ao useSuspense(), mas retorna os valores [D]ata [L]oading [E]rror.

useDLE() reage às mutações de dados, renderizando novamente somente quando necessário.

Uso​

<script setup lang="ts">
  import { useDLE } from '@data-client/vue';
  import { ProfileResource } from './ProfileResource';

  const { data, loading, error } = useDLE(ProfileResource.getList);
</script>

<template>
  <div v-if="error">Error {{ error.status }}</div>
  <Loading v-else-if="loading || !data" />
  <div v-else>
    <div class="listItem" v-for="profile in data" :key="profile.pk()">
      <Avatar :src="profile.avatar" />
      <div>
        <h4>{{ profile.fullName }}</h4>
        <p>{{ profile.bio }}</p>
      </div>
    </div>
  </div>
</template>

Comportamento​

data, loading e error são, cada um, um ComputedRef. Desestruture-os no nível superior do <script setup> para que sejam desembrulhados no template. A tabela abaixo descreve o .value deles.

Status de expiraçãoFetchDataLoadingErrorCondições
Inválidosim1undefinedtruefalsenão está na store, exclusão, invalidação, invalidIfStale
Desatualizadosim1desnormalizadofalsefalse(primeira renderização, mudança de args) & expiração < agora
Válidonãodesnormalizadofalsetalvez2conclusão do fetch
nãoundefinedfalsefalsenull usado como segundo argumento
observação
  1. Fetches idênticos são automaticamente deduplicados
  2. Erros hard devem ser capturados por onErrorCaptured()
Dependências condicionais

Usar null como segundo argumento de qualquer hook do Data Client significa "não fazer nada."

// todo could be undefined if id is undefined
const todo = useDLE(
TodoResource.get,
computed(() => (id.value ? { id: id.value } : null)),
);

Tipos​

function useDLE(
endpoint: ReadEndpoint,
...args: MaybeRefsOrGetters<Parameters<typeof endpoint>> | [null]
): {
data: ComputedRef<DenormalizeNullable<typeof endpoint.schema>>;
loading: ComputedRef<boolean>;
error: ComputedRef<ErrorTypes | undefined>;
};

Os argumentos podem ser valores simples, refs (incluindo computed) ou funções getter como () => ({ id: props.id }). Um objeto simples como { id: props.id } é lido uma única vez e não acompanha mudanças de props ou de rota, então use um getter ou computed quando um argumento puder mudar.

Os resultados são atualizados quando os argumentos mudam.

Exemplos​

Detalhe​

<script setup lang="ts">
  import { useDLE } from '@data-client/vue';
  import { ProfileResource } from './ProfileResource';

  const {
    data: profile,
    loading,
    error,
  } = useDLE(ProfileResource.get, { id: 1 });
</script>

<template>
  <div v-if="error">Error {{ error.status }}</div>
  <Loading v-else-if="loading || !profile" />
  <div v-else class="listItem">
    <Avatar :src="profile.avatar" />
    <div>
      <h4>{{ profile.fullName }}</h4>
      <p>{{ profile.bio }}</p>
    </div>
  </div>
</template>

Condicional​

null evita vincular e buscar os dados

▶Resources
▶PostWithAuthor.vue
<script setup lang="ts">
  import { computed } from 'vue';
  import { useDLE } from '@data-client/vue';
  import { PostResource, UserResource } from './Resources';

  const props = defineProps<{ id: string }>();
  const {
    data: post,
    loading: postLoading,
    error: postError,
  } = useDLE(PostResource.get, () => ({ id: props.id }));
  const {
    data: author,
    loading: authorLoading,
    error: authorError,
  } = useDLE(
    UserResource.get,
    computed(() =>
      post.value?.userId
        ? {
            id: post.value.userId,
          }
        : null,
    ),
  );
</script>

<template>
  <div v-if="postError">Error {{ postError.status }}</div>
  <Loading v-else-if="postLoading || !post" />
  <div v-else-if="authorError">Error {{ authorError.status }}</div>
  <Loading v-else-if="authorLoading || !author" />
  <div v-else>{{ author.username }}</div>
</template>

Dados incorporados​

Quando as entidades são armazenadas em estruturas aninhadas, essa estrutura é mantida.

▶api/Post
import { Entity, RestEndpoint, Collection } from '@data-client/rest';

export class PaginatedPost extends Entity {
  id = '';
  title = '';
  content = '';

  static key = 'PaginatedPost';
}

export const getPosts = new RestEndpoint({
  path: '/post',
  searchParams: { page: '' },
  schema: {
    results: new Collection([PaginatedPost]),
    nextPage: '',
    lastPage: '',
  },
});
▶ArticleList.vue
<script setup lang="ts">
  import { useDLE } from '@data-client/vue';
  import { getPosts } from './api/Post';

  const props = defineProps<{ page: string }>();
  const { data, loading, error } = useDLE(getPosts, () => ({ page: props.page }));
</script>

<template>
  <div v-if="error">Error {{ error.status }}</div>
  <Loading v-else-if="loading || !data" />
  <div v-else>
    <div v-for="post in data.results" :key="post.pk()">
      {{ post.title }}
    </div>
  </div>
</template>