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
Fresh
Dados nesse estado são considerados novos o suficiente para que não precisem de fetch.
Stale
Os dados ainda podem ser exibidos, porém o Reactive Data Client pode tentar revalidá-los fazendo fetch novamente.
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.
Ao usar o React Navigation, eventos de foco também disparam fetches para dados desatualizados.
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.
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.
import { lastUpdated } from './api/lastUpdated'; export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 });
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> ); }
@data-client/rest
Longa duração de cache
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
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.
import { lastUpdated } from './api/lastUpdated'; export const getUpdated = lastUpdated.extend({ invalidIfStale: true, dataExpiryLength: 5000, });
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> ); }
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'.
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 />);
Atualizar endpoints visíveis
Controller.expireAll() define o status de expiração de todas as respostas que correspondem a testKey como Stale.
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 />);
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.
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 />);
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.
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 />);
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 };
}
}