Skip to main content

Rest Hooks 2.2 Released

· 4 min read
Nathaniel Tucker

2.2 comes with the eagerly awaited programmable optimistic updates. This enables two very important use cases: optimistic update on create and infinite pagination.

The bigger part of this release is introducing two new hooks that enable an incremental migration path to 3.0 planned changes. useCacheNew() and useResourceNew() are added in this release, allowing incremental adoption of the new selection logic that will become the default in 3.0. More details below.

Optimistic update on create

Mutation fetches can often change more than just the entities in their response. For example, issuing a create often means a subsequent request for a list of that resource will include the newly created entity. Previously the workaround for this case would be to issue a fetch immediately after the mutation request. This is not ideal as it causes unnecessary network traffic, server load, and most importantly a slower user experience.

However, since updates like these can be extremely varied and implementation-dependant, providing a solution that is protocol agnostic requires careful tuning. Inverting control can also be dangerous as a small bug in user-code can destroy the entire cache structure.

Introducing updateParams a new third argument to the imperative fetch function.

Each tuple in the array represents a result entry to update. The first two members of the tuple represent that result, while the third is a function defining how to update it with the results of the fetch call. TypeScript is invaluable here, as the very strict types ensure the function does not cause any cache state invariants to be violated.

const createArticle = useFetcher(ArticleResource.createShape());

createArticle({ id: 1 }, {}, [
[
ArticleResource.listShape(),
{},
(newArticle, articles) => [...articles, newArticle],
],
]);

Pagination

This can also be used to support pagination. Every time a new page is called, the results of that page can be aggregated on one result list. Here's the rough idea (this code hasn't been tested):

class PaginatedArticleResource extends Resource {
readonly id: number | null = null;
readonly title: string = '';
readonly content: string = '';
readonly author: number | null = null;
readonly tags: string[] = [];

pk() {
return this.id;
}

static urlRoot = 'http://test.com/article/';

static listShape<T extends typeof Resource>(this: T) {
return {
...super.listShape(),
schema: { results: [this.getEntitySchema()] },
};
}
}

function mergeArticles(
newPage: { results: string[] },
articles: { results?: string[] },
): { results: string[] } {
return [...(articles.results || []), ...newPage.results];
}

function useNextPageFetcher() {
const getNextPage = useFetcher(ArticleResource.listShape());
return useCallback(() => {
return getNextPage({}, { cursor: 2 }, [
[ArticleResource.listShape(), {}, mergeArticles],
]);
}, [getNextPage]);
}

Add optimistic update on create + configurable optimistic updates

useResourceNew() and useCacheNew()

Previously for ‘convenience’ Rest Hooks’ selectors would automatically ‘dive’ into results - directly returning the first entity or list of entities it found. This seemed convenient but had the assumption that there would 1) always be an entity in a schema 2) be only one top-level entity. This is a pretty big assumption to make, and violates the principal of protocol agnosticism. Furthermore, this somewhat arbitrary behavior is not obvious and easily results in confusion to new users. Since the legacy selector behavior can easily be composed on top of selectors returning the entire results, we have decided to move to a less opinionated version for the two selector hooks - useCache() and useResource().

To make the transition easy, there are three phases. The first is in 2.2, which will introduce an opt-in to the new behavior by introducing useResourceNew() and useCacheNew(). Upgrading to 2.2 allows incremental adoption of the new behavior one component at a time. Then in 3.0, the current useResource() and useCache() will be renamed to useResourceLegacy() and useCacheLegacy() respectively. The new hooks will then take their place. This makes upgrading a simple rename as well, providing an additional time window of incremental adoption. However, at that point these hooks should be considered deprecrated. Finally, in the next minor/major version, the legacy hooks will be removed altogether.

Add useResourceNew() and useCacheNew()

useResetter()

useResetter() is a simple hook that makes clearing the entire cache imperatively quite simple. This can be useful in cases such as a user changing authentication state by login/logout.

Add useResetter() which returns a function that can clear entire cache

Improved Union and Values support

Additional tests and edge cases using Union and Value types have been added ensuring maximum schema flexibility.

Fully support Union and Values schemas in types and useDenormalized()

Final notes

Please try out the new release and give feedback if there are any issues or if things are working great! We've got a lot of great new features coming down the pipeline and we hope to see your contributions as well. 😊

Full set of 2.2 release commits