Skip to main content

Data mutations

Tell react to update

Just like setState(), we must make React aware of the any mutations so it can rerender.

Controller from useController provides this functionality in a type-safe manner. Controller.fetch() lets us trigger async mutations.

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 { useSuspense } from '@rest-hooks/react';
import { TodoResource } from './api/Todo';
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(TodoResource.get, { id });
const ctrl = useController();
const updateWith = title => () =>
ctrl.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

Rest Hooks uses the fetch response to safely update all components. This not only more than doubles performance, but dramatically reduces server load that comes up sequential fetches.

Tracking imperative loading/error state

useLoading() enhances async functions by tracking their loading and error states.

import { useController } from '@rest-hooks/react';
import { useLoading } from '@rest-hooks/hooks';

function ArticleEdit() {
const ctrl = useController();
const [handleSubmit, loading, error] = useLoading(
data => ctrl.fetch(todoUpdate, { id }, data),
[ctrl],
);
return <ArticleForm onSubmit={handleSubmit} loading={loading} />;
}

React 18 version with useTransition

import { useTransition } from 'react';
import { useController } from '@rest-hooks/react';
import { useLoading } from '@rest-hooks/hooks';

function ArticleEdit() {
const ctrl = useController();
const [loading, startTransition] = useTransition();
const handleSubmit = data =>
startTransition(() => ctrl.fetch(todoUpdate, { id }, data));
return <ArticleForm onSubmit={handleSubmit} loading={loading} />;
}

Zero delay mutations

Controller.fetch call the mutation endpoint, and update React based on the response. While useTransition improves the experience, the UI still ultimately waits on the fetch completion to update.

For many cases like toggling todo.completed, incrementing an upvote, or dragging and drop a frame this can be too slow!

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) {
// for demo purposes we'll only use the first seven
return todos.slice(0, 7);
},
}),
partialUpdate: BaseTodoResource.partialUpdate.extend({
getOptimisticResponse(snap, { id }, body) {
return {
id,
...body,
};
},
}),
};
import { useController } from '@rest-hooks/react';
import { TodoResource, Todo } from './api/Todo';
export function TodoItem({ todo }: { todo: Todo }) {
const ctrl = useController();
return (
<label style={{ display: 'block' }}>
<input
type="checkbox"
checked={todo.completed}
onChange={e =>
ctrl.fetch(
TodoResource.partialUpdate,
{ id: todo.id },
{ completed: e.currentTarget.checked },
)
}
/>
{todo.completed ? <strike>{todo.title}</strike> : todo.title}
</label>
);
}
import { useSuspense } from '@rest-hooks/react';
import { TodoItem } from './TodoItem';
import { TodoResource, Todo } from './api/Todo';
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

getOptimisticResponse is just like setState with an updater function. Using snap for access to the store to get the previous value, as well as the fetch arguments, we return the expected fetch response.

export const updateTodo = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
method: 'PUT',
schema: Todo,
getOptimisticResponse(snap, { id }, body) {
return {
id,
...body,
};
},
});

Rest Hooks ensures data integrity against any possible networking failure or race condition, so don't worry about network failures, multiple mutation calls editing the same data, or other common problems in asynchronous programming.