Pular para o conteúdo principal
observação

O original em inglês mudou depois desta tradução, então ela pode estar desatualizada.

Política de expiração do Endpoint

Por padrão, a política de cache do Reactive Data Client pode ser descrita como stale-while-revalidate. Isso significa que, quando há dados disponíveis, é possível evitar bloquear a aplicação usando os dados desatualizados. No entanto, em segundo plano, ele ainda atualizará os dados se forem antigos o suficiente.

Status de expiração​

dentro de dataExpiryLengthPor quanto tempo (em milissegundos) uma resposta se mantém fresca antes de ficar desatualizada. Saiba mais
useSuspense()GET

Fresh​

Dados nesse estado são considerados novos o suficiente para que não precisem de fetch.

Quando ligado, os dados pulam Stale e passam a ser inválidos assim que expiram, então precisam ser buscados de novo antes de poderem renderizar. Saiba mais
depois de dataExpiryLengthDepois que dataExpiryLength (em milissegundos) passa, a resposta em cache fica desatualizada: ainda é mostrada, mas é buscada de novo. Saiba mais
useSuspense()GET

Stale​

Os dados ainda podem ser exibidos, porém o Reactive Data Client pode tentar revalidá-los fazendo fetch novamente.

depois de ctrl.invalidate()Marca os dados como inválidos na hora, mesmo que ainda estejam frescos, para que os componentes que precisam deles os busquem de novo. Saiba mais
useSuspense()GET

Invalid​

Os dados não devem ser exibidos. Qualquer componente que precise desses dados disparará o fetch e o suspense. Se nenhum componente se importar com esses dados, nenhuma ação será tomada.

useSuspense() considera fazer fetch na montagem, bem como quando seus parâmetros mudam. Nesses casos, ele fará o fetch se os dados forem considerados desatualizados.

React Native

Ao usar o React Navigation, eventos de foco também disparam fetches para dados desatualizados.

Tempo de expiração​

Endpoint.dataExpiryLength​

Endpoint.dataExpiryLength define quanto tempo (em milissegundos) leva para os dados passarem do status 'fresh' para 'stale'. Experimente definir um número bem baixo, como '50', para que fiquem stale quase instantaneamente; ou um número muito grande para que permaneçam por muito tempo.

Alternar entre 'first' e 'second' muda os parâmetros. Se os dados ainda forem considerados fresh, você continuará vendo o horário antigo, sem nenhuma atualização.

Fixtures
▶api/lastUpdated
▶getUpdated
import { lastUpdated } from './api/lastUpdated';

export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 });
▶TimePage
import { useSuspense } from '@data-client/react';
import { getUpdated } from './getUpdated';

export default function TimePage({ id }) {
  const { updatedAt } = useSuspense(getUpdated, { id });
  return (
    <div>
      API time for {id}:{' '}
      <time>
        {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })}
      </time>
    </div>
  );
}
▶Navigator
Resultado
Store▶
@data-client/rest

Longa duração de cache

LongLivingResource.ts
import {
RestEndpoint,
RestGenerics,
resource,
} from '@data-client/rest';

// We can now use LongLivingEndpoint to create endpoints that will be cached for one hour
class LongLivingEndpoint<
O extends RestGenerics,
> extends RestEndpoint<O> {
dataExpiryLength = 60 * 60 * 1000; // one hour
}

const LongLivingResource = resource({
path: '/:id',
Endpoint: LongLivingEndpoint,
});

Nunca tentar novamente em caso de erro

NoRetryResource.ts
import {
RestEndpoint,
RestGenerics,
resource,
} from '@data-client/rest';

// We can now use NoRetryEndpoint to create endpoints that will be cached for one hour
class NoRetryEndpoint<
O extends RestGenerics,
> extends RestEndpoint<O> {
errorExpiryLength = Infinity;
}

const NoRetryResource = resource({
path: '/:id',
Endpoint: NoRetryEndpoint,
});

Endpoint.invalidIfStale​

Endpoint.invalidIfStale elimina o status 'stale', fazendo com que dados que expiram sejam imediatamente considerados 'invalid'.

Isso é demonstrado pelo componente que suspende assim que seus dados ficam stale. Se os dados ainda estiverem dentro do tempo de expiração, ele simplesmente continua a exibi-los.

Fixtures
▶api/lastUpdated
▶getUpdated
import { lastUpdated } from './api/lastUpdated';

export const getUpdated = lastUpdated.extend({
  invalidIfStale: true,
  dataExpiryLength: 5000,
});
▶TimePage
import { useSuspense } from '@data-client/react';
import { getUpdated } from './getUpdated';

export default function TimePage({ id }) {
  const { updatedAt } = useSuspense(getUpdated, { id });
  return (
    <div>
      API time for {id}:{' '}
      <time>
        {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })}
      </time>
    </div>
  );
}
▶Navigator
Resultado
Store▶

Forçar atualização​

Às vezes queremos buscar dados novos, continuando a mostrar os dados antigos (stale).

Um endpoint específico​

Controller.fetch pode ser usado para disparar um fetch enquanto ainda se mostram os dados anteriores. Isso pode ser feito mesmo com dados 'fresh'.

Fixtures
▶api/lastUpdated
▶ShowTime
import { useSuspense, useController } from '@data-client/react';
import { lastUpdated } from './api/lastUpdated';

function ShowTime() {
  const { updatedAt } = useSuspense(lastUpdated, { id: '1' });
  const ctrl = useController();
  return (
    <div>
      <time>
        {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })}
      </time>{' '}
      <button onClick={() => ctrl.fetch(lastUpdated, { id: '1' })}>
        Refresh
      </button>
    </div>
  );
}
render(<ShowTime />);
Resultado
Store▶

Atualizar endpoints visíveis​

Controller.expireAll() define o status de expiração de todas as respostas que correspondem a testKey como Stale.

Fixtures
▶api/lastUpdated
▶ShowTime
▶Loading
▶Demo
import { AsyncBoundary, useController } from '@data-client/react';

import { lastUpdated } from './api/lastUpdated';
import ShowTime from './ShowTime';
import Loading from './Loading';

function Demo() {
  const ctrl = useController();
  return (
    <div>
      <AsyncBoundary fallback={<Loading id="1" />}>
        <ShowTime id="1" />
      </AsyncBoundary>
      <AsyncBoundary fallback={<Loading id="2" />}>
        <ShowTime id="2" />
      </AsyncBoundary>
      <AsyncBoundary fallback={<Loading id="3" />}>
        <ShowTime id="3" />
      </AsyncBoundary>

      <button onClick={() => ctrl.expireAll(lastUpdated)}>
        Expire All
      </button>
      <button onClick={() => ctrl.fetch(lastUpdated, { id: '1' })}>
        Force Refresh First
      </button>
    </div>
  );
}
render(<Demo />);
Resultado
Store▶

Invalidate (suspender novamente)​

Tanto endpoints quanto entities podem ser alvos de invalidação.

Um endpoint específico​

Neste exemplo, invalidar o endpoint exibe o fallback de carregamento, já que os dados não podem ser exibidos.

Fixtures
▶api/lastUpdated
▶ShowTime
▶Loading
▶Demo
import { AsyncBoundary, useController } from '@data-client/react';

import { lastUpdated } from './api/lastUpdated';
import ShowTime from './ShowTime';
import Loading from './Loading';

function Demo() {
  const ctrl = useController();
  return (
    <div>
      <AsyncBoundary fallback={<Loading id="1" />}>
        <ShowTime id="1" />
      </AsyncBoundary>
      <AsyncBoundary fallback={<Loading id="2" />}>
        <ShowTime id="2" />
      </AsyncBoundary>
      <AsyncBoundary fallback={<Loading id="3" />}>
        <ShowTime id="3" />
      </AsyncBoundary>

      <button onClick={() => ctrl.invalidateAll(lastUpdated)}>
        Invalidate All
      </button>
      <button
        onClick={() => ctrl.invalidate(lastUpdated, { id: '1' })}
      >
        Invalidate First
      </button>
    </div>
  );
}
render(<Demo />);
Resultado
Store▶

Qualquer endpoint com uma entity​

Usar o schema Invalidate nos permite invalidar qualquer endpoint que dependa dessa entity em sua resposta. Se o endpoint usa a entity em um Array, ela simplesmente será removida desse Array.

Fixtures
▶api/lastUpdated
▶TimePage
▶ShowTime
import { Invalidate, RestEndpoint } from '@data-client/rest';
import { AsyncBoundary, useController, useLoading } from '@data-client/react';
import { TimedEntity } from './api/lastUpdated';
import TimePage from './TimePage';

const InvalidateTimedEntity = new Invalidate(TimedEntity);
export const deleteLastUpdated = new RestEndpoint({
  path: '/api/currentTime/:id',
  method: 'DELETE',
  schema: InvalidateTimedEntity,
});

function ShowTime() {
  const ctrl = useController();
  const [handleDelete, loadingDelete] = useLoading(
    () => ctrl.fetch(deleteLastUpdated, { id: '1' }),
    [],
  );
  return (
    <div>
      <AsyncBoundary fallback={<div>loading...</div>}>
        <TimePage id="1" />
      </AsyncBoundary>
      <div>
        Current Time: <CurrentTime />
      </div>
      <button onClick={handleDelete}>
        {loadingDelete ? 'loading...' : 'Invalidate'}
      </button>
      <button
        onClick={() =>
          ctrl.setResponse(
            deleteLastUpdated,
            { id: '1' },
            { id: '1' },
          )
        }
      >
        Invalidate (without fetching DELETE)
      </button>
      <button
        onClick={() => ctrl.set([InvalidateTimedEntity], [{ id: '1' }])}
      >
        Invalidate Entity with ctrl.set
      </button>
    </div>
  );
}
render(<ShowTime />);
Resultado
Store▶

Controller.fetch() nos permite atualizar o servidor e o store. Podemos usar Controller.setResponse() ou Controller.set() quando queremos alterar diretamente o store local.

Invalidação condicional com base nos dados​

Se a invalidation deve acontecer apenas às vezes, com base nos dados da resposta, podemos retornar undefined de Entity.process.

class PriceLevel extends Entity {
price = 0;
amount = 0;

pk() {
return this.price;
}

static process(
input: [number, number],
parent: any,
key: string | undefined,
): any {
const [price, amount] = input;
if (amount === 0) return undefined;
return { price, amount };
}
}