Skip to main content

Invalidate

Describes entities to be marked as INVALID. This removes items from a collection, or forces suspense for endpoints where the entity is required.

Constructor

new Invalidate(entity)
new Invalidate(union)
new Invalidate(entityMap, schemaAttribute)
  • entity: A singular Entity to invalidate.
  • union: A Union schema for polymorphic invalidation.
  • entityMap: A mapping of schema keys to Entities.
  • schemaAttribute: optional (required if entityMap is used) The attribute on each entity found that defines what schema, per the entityMap, to use when normalizing. Can be a string or a function. If given a function, accepts the following arguments:
    • value: The input value of the entity.
    • parent: The parent object of the input array.
    • key: The key at which the input array appears on the parent object.

Usage

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 />);
🔴 Live Preview
Store

Batch Invalidation

Here we add another endpoint for deleting many entities at a time by wrapping Invalidate in an array. Data Client can then invalidate every entity from the response.

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)],
});
Request
import { PostResource } from './Resource';
PostResource.deleteMany(['5', '13', '7']);
Request
DELETE /posts
Content-Type: application/json
Body: ["5","13","7"]
Response200
[
{
"id": "5"
},
{
"id": "13"
},
{
"id": "7"
}
]

Sometimes our backend returns nothing for 'DELETE'. In this case, we can use process to build a usable response from the argument 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 }));
  }
});
Request
import { PostResource } from './Resource';
PostResource.deleteMany(['5', '13', '7']);
Request
DELETE /posts
Content-Type: application/json
Body: ["5","13","7"]
Response204
NO CONTENT

Polymorphic types

If your endpoint can delete more than one type of entity, you can use polymorphic invalidation.

With Union schema

The simplest approach is to pass an existing Union schema directly:

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),
});

string schemaAttribute

Alternatively, define the polymorphic mapping inline with a string attribute:

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

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

function schemaAttribute

The return values should match a key in the entity map. This is useful for more complex discrimination logic:

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'
),
});

Impact on useSuspense()

When entities are invalidated in a result currently being presented in React, useSuspense() will consider them invalid

  • For optional Entities, they are simply removed
  • For required Entities, this invalidates the entire response re-triggering suspense.