Pular para o conteúdo principal

Invalidate

Descreve entidades a serem marcadas como INVALID. Isso remove itens de uma collection ou força o suspense em endpoints nos quais a entidade é obrigatória.

Construtor​

new Invalidate(entity)
new Invalidate(union)
new Invalidate(entityMap, schemaAttribute)
  • entity: Uma Entity singular a invalidar.
  • union: Um schema Union para invalidação polimórfica.
  • entityMap: Um mapeamento de chaves de schema para Entities.
  • schemaAttribute: opcional (obrigatório se entityMap for usado) O atributo de cada entidade encontrada que define qual schema, conforme o entityMap, usar na normalização. Pode ser uma string ou uma função. Se for uma função, recebe os seguintes argumentos:
    • value: O valor de entrada da entidade.
    • parent: O objeto pai do array de entrada.
    • key: A chave sob a qual o array de entrada aparece no objeto pai.

Uso​

Fixtures
GET /users
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"},{"id":"555","name":"Phone"}]
DELETE /users/:id
▶api/User
import { Entity, RestEndpoint, Collection, Invalidate } from '@data-client/rest';

class User extends Entity {
  id = '';
  name = '';
}
export const getUsers = new RestEndpoint({
  path: '/users',
  schema: new Collection([User]),
});
export const deleteUser = new RestEndpoint({
  path: '/users/:id',
  method: 'DELETE',
  schema: new Invalidate(User),
});
▶UserPage
import { useSuspense, useController } from '@data-client/react';
import { getUsers, deleteUser } from './api/User';

function UsersPage() {
  const users = useSuspense(getUsers);
  const ctrl = useController();
  return (
    <div>
      {users.map(user => (
        <div key={user.pk()}>
          {user.name}{' '}
          <span
            style={{ cursor: 'pointer' }}
            onClick={() => ctrl.fetch(deleteUser, { id: user.id })}
          >
            ❌
          </span>
        </div>
      ))}
    </div>
  );
}
render(<UsersPage />);
Resultado
Store▶

Invalidação em lote​

Aqui adicionamos outro endpoint para excluir várias entidades de uma só vez, envolvendo Invalidate em um array. O Data Client pode então fazer invalidate de cada entidade da resposta.

import { resource, Invalidate } from '@data-client/rest';
import Post from './Post';

export const PostResource = resource({
  schema: Post,
  path: '/posts/:id',
}).extend('deleteMany', {
  path: '/posts',
  body: [] as string[],
  method: 'DELETE',
  schema: [new Invalidate(Post)],
});
▶Usage
import { PostResource } from './Resource';
PostResource.deleteMany(['5', '13', '7']);
Request
DELETE /posts
content-type: application/json
Body: ["5","13","7"]
Response200 OK
[
{
"id": "5"
},
{
"id": "13"
},
{
"id": "7"
}
]

Às vezes o nosso backend não retorna nada para 'DELETE'. Nesse caso, podemos usar process para construir uma resposta utilizável a partir do argumento body.

import { resource, Invalidate } from '@data-client/rest';
import Post from './Post';

export const PostResource = resource({
  schema: Post,
  path: '/posts/:id',
}).extend('deleteMany', {
  path: '/posts',
  body: [] as string[],
  method: 'DELETE',
  schema: [new Invalidate(Post)],
  process(value, body) {
    // use the body payload to inform which entities to delete
    return body.map(id => ({ id }));
  }
});
▶Usage
import { PostResource } from './Resource';
PostResource.deleteMany(['5', '13', '7']);
Request
DELETE /posts
content-type: application/json
Body: ["5","13","7"]
Response204 No Content
NO CONTENT

Para excluir várias entidades sem um endpoint, como a partir de uma mensagem de websocket, passe o mesmo schema para Controller.set():

ctrl.set([new Invalidate(Post)], [{ id: '5' }, { id: '13' }, { id: '7' }]);

Ou exclua uma única entidade:

ctrl.set(new Invalidate(Post), { id: '5' });

Tipos polimórficos​

Se o seu endpoint pode excluir mais de um tipo de entidade, você pode usar a invalidação polimórfica.

Com o schema Union​

A abordagem mais simples é passar diretamente um schema Union existente:

import { Entity, RestEndpoint, Union, Invalidate } from '@data-client/rest';

class User extends Entity {
id = '';
name = '';
readonly type = 'users';
}
class Group extends Entity {
id = '';
groupname = '';
readonly type = 'groups';
}

const MemberUnion = new Union(
{ users: User, groups: Group },
'type'
);

const deleteMember = new RestEndpoint({
path: '/members/:id',
method: 'DELETE',
schema: new Invalidate(MemberUnion),
});

schemaAttribute string​

Como alternativa, defina o mapeamento polimórfico inline com um atributo string:

import { RestEndpoint, Invalidate } from '@data-client/rest';

const deleteMember = new RestEndpoint({
path: '/members/:id',
method: 'DELETE',
schema: new Invalidate(
{ users: User, groups: Group },
'type'
),
});

schemaAttribute função​

Os valores retornados devem corresponder a uma chave no mapa de entidades. Isso é útil para lógicas de discriminação mais complexas:

import { RestEndpoint, Invalidate } from '@data-client/rest';

const deleteMember = new RestEndpoint({
path: '/members/:id',
method: 'DELETE',
schema: new Invalidate(
{ users: User, groups: Group },
(input, parent, key) => input.memberType === 'user' ? 'users' : 'groups'
),
});

Impacto no useSuspense()​

Quando entidades são invalidadas em um resultado que está sendo apresentado no React, useSuspense() as considerará inválidas

  • Para Entities opcionais, elas são simplesmente removidas
  • Para Entities obrigatórias, isso invalida a resposta inteira, disparando o suspense novamente.