Skip to main content
Version: 6.5

Entity and Data Normalization

Entities have a primary key. This enables easy access via a lookup table. This makes it easy to find, update, create, or delete the same data - no matter what endpoint it was used in.

Entities cache

Extracting entities from a response is known as normalization. Accessing a response reverses the process via denormalization.

Global Referential Equality

Using entities expands Rest Hooks' global referential equality guarantee beyond the granularity of an entire endpoint response.

Mutations and Dynamic Data

When an endpoint changes data, this is known as a side effect. Marking an endpoint with sideEffect: true tells Rest Hooks that this endpoint is not idempotent, and thus should not be allowed in hooks that may call the endpoint an arbitrary number of times like useSuspense() or useFetch()

By including the changed data in the endpoint's response, Rest Hooks is able to able to update any entities it extracts by specifying the schema.

import { schema, Endpoint } from '@rest-hooks/endpoint';

const todoCreate = new Endpoint(
(body: FormData) =>
fetch(`https://jsonplaceholder.typicode.com/todos/`, {
method: 'POST',
body,
}).then(res => res.json()),
{ schema: Todo, sideEffect: true },
);
Example Usage
import { useController } from 'rest-hooks';

export default function NewTodoForm() {
const { fetch } = useController();
return (
<Form onSubmit={e => fetch(todoCreate, new FormData(e.target))}>
<FormField name="title" />
</Form>
);
}
info

Mutations automatically update the normalized cache, resulting in consistent and fresh data.

Schema

Schemas are a declarative definition of how to process responses

import { Endpoint } from '@rest-hooks/endpoint';

const fetchTodoList = (params: any) =>
fetch(`https://jsonplaceholder.typicode.com/todos/`).then(res => res.json());

const todoList = new Endpoint(fetchTodoList, {
schema: [Todo],
sideEffect: true,
});

Placing our Entity Todo in an array, tells Rest Hooks to expect an array of Todos.

Aside from array, there are a few more 'schemas' provided for various patterns. The first two (Object and Array) have shorthands of using object and array literals.

  • Object: maps with known keys
  • Array: variably sized arrays
  • Union: select from many different types
  • Values: maps with any keys - variably sized
  • Delete: remove an entity

Learn more

Nesting

Additionally, Entities themselves can specify nested schemas by specifying a static schema member.

import { Entity } from '@rest-hooks/endpoint';

class Todo extends Entity {
readonly id: number = 0;
readonly user: User = User.fromJS({});
readonly title: string = '';
readonly completed: boolean = false;

pk() {
return `${this.id}`;
}

static schema = {
user: User,
};
}

class User extends Entity {
readonly id: number = 0;
readonly username: string = '';

pk() {
return `${this.id}`;
}
}

Learn more

Data Representations

Additionally, any newable class that has a toJSON() method, can be used as a schema. This will simply construct the object during denormalization. This might be useful with representations like bignumber

import { Entity } from '@rest-hooks/endpoint';

class Todo extends Entity {
readonly id: number = 0;
readonly user: User = User.fromJS({});
readonly title: string = '';
readonly completed: boolean = false;
readonly dueDate: Date = new Date(0);

pk() {
return `${this.id}`;
}

static schema = {
user: User,
dueDate: Date,
};
}
info

Due to the global referential equality guarantee - construction of members only occurs once per update.