Skip to main content

A simple data fetch

Add a single useSuspense() call where you need its data.

Rest Hooks automatically optimizes performance by caching the results, deduplicating fetches, efficient component render bindings and more.

Editor
export const getTodo = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
});
import { getTodo } from './api';
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(getTodo, { id });
return <div>{todo.title}</div>;
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store
Editor
export class Todo extends Entity {
id = 0;
userId = 0;
title = '';
completed = false;
pk() { return `${this.id}` }
}
export const TodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
})
import { TodoResource } from './api';
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(TodoResource.get, { id });
return <div>{todo.title}</div>;
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store
Editor
import { GQLEndpoint } from '@rest-hooks/graphql';
const gql = new GQLEndpoint('/');
export const getTodo = gql.query(`
query GetTodo($id: ID!) {
todo(id: $id) {
id
title
completed
}
}
`);
import { getTodo } from './api';
function TodoDetail({ id }: { id: number }) {
const { todo } = useSuspense(getTodo, { id });
return <div>{todo.title}</div>;
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store

Stateful mutations

Use Controller.fetch() to update the store.

Rest Hooks ensures data consistency and integrity globally. Every piece of data maintains referential stability unless it changes. This ensures the most optimized render performance, as well as predictable equality checks.

Editor
export class Todo extends Entity {
id = 0;
userId = 0;
title = '';
completed = false;
pk() { return `${this.id}` }
}
const BaseTodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
});
export const TodoResource = {
...BaseTodoResource,
partialUpdate: BaseTodoResource.partialUpdate.extend({
getOptimisticResponse(snap, params, body) {
return {
id: params.id,
...body,
};
},
}),
};
import { TodoResource } from './api';
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(TodoResource.get, { id });
const controller = useController();
const updateWith = title => () =>
controller.fetch(
TodoResource.partialUpdate,
{ id },
{ title }
);
return (
<div>
<div>{todo.title}</div>
<button onClick={updateWith('🥑')}>🥑</button>
<button onClick={updateWith('💖')}>💖</button>
</div>
);
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store
Editor
import { GQLEndpoint, GQLEntity } from '@rest-hooks/graphql';
const gql = new GQLEndpoint('/');
export class Todo extends GQLEntity {
readonly title: string = '';
readonly completed: boolean = false;
}
export const getTodo = gql.query(`
query GetTodo($id: ID!) {
todo(id: $id) {
id
title
completed
}
}
`, { todo: Todo });
export const updateTodo = gql.mutation(
`mutation UpdateTodo($todo: Todo!) {
updateTodo(todo: $todo) {
id
title
completed
}
}`,
{ updateTodo: Todo },
);
import { getTodo, updateTodo } from './api';
function TodoDetail({ id }: { id: number }) {
const { todo } = useSuspense(getTodo, { id });
const controller = useController();
const updateWith = title => () =>
controller.fetch(
updateTodo,
{ todo: { id, title } }
);
return (
<div>
<div>{todo.title}</div>
<button onClick={updateWith('🥑')}>🥑</button>
<button onClick={updateWith('💖')}>💖</button>
</div>
);
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store

An application

Data can be consumed and controlled in many contexts, speeding up development.

Rest Hooks uses data normalization to maintain consistency no matter how and where the data is consumed.

Rest easy with the help of debugging, unit testing, and storybook integration.

Editor
export class Todo extends Entity {
id = 0;
userId = 0;
title = '';
completed = false;
pk() { return `${this.id}` }
}
const BaseTodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
});
export const TodoResource = {
...BaseTodoResource,
getList: BaseTodoResource.getList.extend({
process(todos) {
return todos.slice(0, 7);
},
}),
partialUpdate: BaseTodoResource.partialUpdate.extend({
getOptimisticResponse(snap, params, body) {
return {
id: params.id,
...body,
};
},
}),
delete: BaseTodoResource.delete.extend({
getOptimisticResponse(snap, params) {
return params;
},
}),
};
import { TodoResource, Todo } from './api';
function TodoItem({ todo }: { todo: Todo }) {
const controller = useController();
return (
<div>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={e =>
controller.fetch(
TodoResource.partialUpdate,
{ id: todo.id },
{ completed: e.currentTarget.checked },
)
}
/>
{todo.completed ? (
<strike>{todo.title}</strike>
) : (
todo.title
)}
</label>
<span
style={{ cursor: 'pointer' }}
onClick={() =>
controller.fetch(TodoResource.delete, {
id: todo.id,
})
}
>
</span>
</div>
);
}
function TodoList() {
const todos = useSuspense(TodoResource.getList);
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.pk()} todo={todo} />
))}
</div>
);
}
render(<TodoList />);
Live Preview
Loading...
Store
Editor
import { GQLEndpoint, GQLEntity } from '@rest-hooks/graphql';
const gql = new GQLEndpoint('/');
export class Todo extends GQLEntity {
readonly title: string = '';
readonly completed: boolean = false;
}
export const todoList = gql.query(`
query GetTodos {
todo {
id
title
completed
}
}
`, { todos: [Todo] });
export const updateTodo = gql.mutation(
`mutation UpdateTodo($todo: Todo!) {
updateTodo(todo: $todo) {
id
title
completed
}
}`,
{ updateTodo: Todo },
);
import { todoList, updateTodo, Todo } from './api';
function TodoItem({ todo }: { todo: Todo }) {
const controller = useController();
return (
<div>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={e =>
controller.fetch(updateTodo, {
todo: { id: todo.id, completed: e.currentTarget.checked },
})
}
/>
{todo.completed ? <strike>{todo.title}</strike> : todo.title}
</label>
</div>
);
}
function TodoList() {
const { todos } = useSuspense(todoList, {});
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.pk()} todo={todo} />
))}
</div>
);
}
render(<TodoList />);
Live Preview
Loading...
Store
Data IntegrityData Integrity

Data Integrity

Strong inferred types; single source of truth that is referentially stable ensures consistency; asynchronous invariants make it easy to avoid race conditions

Performance

Normalized cache means data is often ready before it is even needed. Automatic request deduplication means less data to send over the network.

Composition over configuration

Declare what you need where you need it. Share data definitions across platforms, components, protocols, and behaviors.

Incremental Adoption

Get started fast with one line data definition and one line data binding. Then add TypeScript, normalized cache with Schemas, optimistic updates and more.