Skip to main content

v8 Controller.fetch, async getHeaders, Collections

· 6 min read
Nathaniel Tucker

Collections enable Arrays and Objects to be easily extended by pushing or unshifting new members. The namesake comes from Backbone Collections.

Collections were first introduced in @rest-hooks/[email protected]. @rest-hooks/rest@7 takes this one step further, but using them in Resource.getList.

Fixtures
GET /users
async response(...args){return(await UserResource.getList(...args)).slice(0,3);}
GET /todos
async response(...args){return(await TodoResource.getList(...args)).slice(0,7);}
PATCH /todos/:id
async response(...args){return{...(await TodoResource.partialUpdate(...args)),id:args?.[0]?.id};}
POST /todos
async response(...args){//await new Promise(resolve => setTimeout(resolve, 500));
return{...(await TodoResource.getList.push(...args)),id:randomId()};}
TodoResource
import { Entity } from '@rest-hooks/rest';
import { createResource } from '@rest-hooks/rest/next';
export class Todo extends Entity {
id = 0;
userId = 0;
title = '';
completed = false;
pk() {
return `${this.id}`;
}
static key = 'Todo';
}
export const TodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
searchParams: {} as { userId?: string | number } | undefined,
schema: Todo,
optimistic: true,
});
TodoItem
import { useController } from '@rest-hooks/react/next';
import { TodoResource, type Todo } from './TodoResource';
export default function TodoItem({ todo }: { todo: Todo }) {
const ctrl = useController();
const handleChange = e =>
ctrl.fetch(
TodoResource.partialUpdate,
{ id: todo.id },
{ completed: e.currentTarget.checked },
);
const handleDelete = () =>
ctrl.fetch(TodoResource.delete, {
id: todo.id,
});
return (
<div className="listItem nogap">
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={handleChange}
/>
{todo.completed ? <strike>{todo.title}</strike> : todo.title}
</label>
<CancelButton onClick={handleDelete} />
</div>
);
}
CreateTodo
import { useController } from '@rest-hooks/react/next';
import { TodoResource } from './TodoResource';
export default function CreateTodo({ userId }: { userId: number }) {
const ctrl = useController();
const handleKeyDown = async e => {
if (e.key === 'Enter') {
ctrl.fetch(TodoResource.getList.push, {
userId,
title: e.currentTarget.value,
id: Math.random(),
});
e.currentTarget.value = '';
}
};
return (
<div className="listItem nogap">
<label>
<input type="checkbox" name="new" checked={false} disabled />
<input type="text" onKeyDown={handleKeyDown} />
</label>
</div>
);
}
TodoList
import { useSuspense } from '@rest-hooks/react/next';
import { TodoResource } from './TodoResource';
import TodoItem from './TodoItem';
import CreateTodo from './CreateTodo';
function TodoList() {
const userId = 1;
const todos = useSuspense(TodoResource.getList, { userId });
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.pk()} todo={todo} />
))}
<CreateTodo userId={userId} />
</div>
);
}
render(<TodoList />);
Live Preview
Loading...
Store

Upgrading is quite simple, as @rest-hooks/rest/next and @rest-hooks/react/next were introduced to allow incremental adoption of the new APIs changed in this release. This makes the actual upgrade a simple import rename.

Other highlights include

Upgrade to Rest Hooks 8 guide

@rest-hooks/react 8.0

https://github.com/reactive/data-client/releases/tag/%40rest-hooks%2Freact%408.0.0

Upgrading can be done gradually as all changes were initially released in /next.

  1. Upgrade to the latest v7:

    npm install --save @rest-hooks/[email protected]
  2. Incrementally move to new versions by importing from /next

    import { useController } from '@rest-hooks/react/next';
  3. Upgrade to v8.

    npm install --save @rest-hooks/[email protected]
  4. Imports can be updated incrementally after upgrade. /next exports the same as top-level.

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

Changes

  • Controller.fetch(): 2545 Controller.fetch() returns denormalized form when Endpoint has a Schema

    const handleChange = async e => {
    const todo = await ctrl.fetch(
    TodoResource.partialUpdate,
    { id: todo.id },
    { completed: e.currentTarget.checked },
    );
    // todo is Todo, we can use all its members and be type-safe
    console.log(todo.pk(), todo.title);
    };
  • NetworkManager: NetworkManager interface changed to only support new actions 2690

  • SubscriptionManager/PollingSubscription interfaces simplified based on new actions 2690

Removals of deprecated items

  • 2691: Remove DispatchContext, DenormalizeCacheContext

Deprecations

  • controller.receive, controller.receiveError 2690
  • RECEIVE_TYPE 2690
  • MiddlewareAPI.controller (MiddlewareAPI is just controller itself) 2690
    • ({controller}) => {} -> (controller) => {}

@rest-hooks/rest 7.0

https://github.com/reactive/data-client/releases/tag/%40rest-hooks%2Frest%407.0.0

Upgrading can be done gradually as all changes were initially released in /next.

  1. Upgrade to the latest v6:

    npm install --save @rest-hooks/[email protected]
  2. Incrementally move to new versions by importing from /next

    import {
    RestEndpoint,
    createResource,
    GetEndpoint,
    MutateEndpoint,
    } from '@rest-hooks/rest/next';

    See the migrations of the /examples directory as an example

    If you have a base RestEndpoint and/or createResource function you can simply create two versions, which each extend from @rest-hooks/rest and @rest-hooks/rest/next

    Inheritance migration example
    EndpointBase.ts
     import { RestEndpoint, RestGenerics  } from '@rest-hooks/rest';

    export default class MyEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
    urlPrefix = 'https://api.github.com';

    getHeaders(headers: HeadersInit): HeadersInit {
    return {
    ...headers,
    'Access-Token': getAuth(),
    };
    }
    }
    NextEndpointBase.ts
     import { RestEndpoint, RestGenerics } from '@rest-hooks/rest/next';

    export default class NextMyEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
    urlPrefix = 'https://api.github.com';

    getHeaders(headers: HeadersInit): Promise<HeadersInit> {
    return {
    ...headers,
    'Access-Token': await getAuth(),
    };
    }
    }
  3. Upgrade to v7

    npm install --save @rest-hooks/[email protected]
  4. Imports can be updated incrementally after upgrade. /next exports the same as top-level.

    import { RestEndpoint, createResource } from '@rest-hooks/rest';

Changes

  • RestEndpoint's getRequestInit and getHeaders optionally return a promise 2542

    import { RestEndpoint } from '@rest-hooks/rest/next';

    export default class AuthdEndpoint<
    O extends RestGenerics = any,
    > extends RestEndpoint<O> {
    declare static accessToken?: string;

    async getHeaders(headers: HeadersInit) {
    return {
    ...headers,
    'Access-Token': await getOrFetchToken(),
    } as HeadersInit;
    }
    }

    export const TodoResource = createResource({
    urlPrefix: 'https://jsonplaceholder.typicode.com',
    path: '/todos/:id',
    schema: Todo,
    Endpoint: AuthdEndpoint,
    });
  • createResource().getList uses a Collection, which .create appends to 2593

    • Resource.create will automatically add to the list
      • Resource.getList.push is identical to Resource.create
    • Remove any Endpoint.update as it is not necessary and will not work
      const createUser = new RestEndpoint({
      path: '/user',
      method: 'POST',
      schema: User,
      // delete the following:
      update: (newUserId: string) => ({
      [userList.key()]: (users = []) => [newUserId, ...users],
      }),
      });
  • GetEndpoint and MutateEndpoint parameters changed to what NewGetEndpoint, NewMutateEndpoint was.

  • createResource() generics changed to O extends ResourceGenerics This allows customizing the Resource type with body and searchParams 2593

    • createGithubResource<U extends string, S extends Schema> -> createGithubResource<O extends ResourceGenerics>

Hoisting /next PR #2692

Removals of deprecated items

  • 2690: Removed deprecated Endpoint.optimisticUpdate -> use Endpoint.getOptimisticResponse
  • 2688 Remove FetchShape compatibility. This removes support for the legacy hooks in 'rest-hooks' like useResource()

Upgrade support

As usual, if you have any troubles or questions, feel free to join our Chat or file a bug