createResource
Resources
are a collection of RestEndpoints that operate on a common
data by sharing a schema
Usage
export class Todo extends Entity {
id = '';
title = '';
completed = false;
pk() {
return this.id;
}
}
const TodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
});
const todo = useSuspense(TodoResource.get, { id: '5' });
const todos = useSuspense(TodoResource.getList);
controller.fetch(TodoResource.create, {
title: 'finish installing rest hooks',
});
controller.fetch(
TodoResource.update,
{ id: '5' },
{ ...todo, completed: true },
);
controller.fetch(TodoResource.partialUpdate, { id: '5' }, { completed: true });
controller.fetch(TodoResource.delete, { id: '5' });
Arguments
{
path: string;
schema: Schema;
Endpoint?: typeof RestEndpoint;
urlPrefix?: string;
} & EndpointExtraOptions
path
Passed to RestEndpoint.path
schema
Passed to RestEndpoint.schema
urlPrefix
Passed to RestEndpoint.urlPrefix
Endpoint
Class used to construct the members.
EndpointExtraOptions
Members
These provide the standard CRUD endpointss common in REST APIs. Feel free to customize or add new endpoints based to match your API.
get
- method: 'GET'
- path:
path
- schema: schema
// GET //test.com/api/abc/xyz
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).get({
group: 'abc',
id: 'xyz',
});
Commonly used with useSuspense(), Controller.invalidate
getList
- method: 'GET'
- path:
shortenPath(path)
- Removes the last argument:
createResource({ path: '/:first/:second' }).getList.path === '/:first';
createResource({ path: '/:first' }).getList.path === '/';
- Removes the last argument:
- schema: [schema]
// GET //test.com/api/abc?isExtra=xyz
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).getList({
group: 'abc',
isExtra: 'xyz',
});
Commonly used with useSuspense(), Controller.invalidate
create
- method: 'POST'
- path:
shortenPath(path)
- Removes the last argument:
createResource({ path: '/:first/:second' }).create.path === '/:first';
createResource({ path: '/:first' }).create.path === '/';
- Removes the last argument:
- schema:
schema
// POST //test.com/api/abc
// BODY { "title": "winning" }
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).create(
{ group: 'abc' },
{ title: 'winning' },
);
Commonly used with Controller.fetch
update
- method: 'PUT'
- path:
path
- schema:
schema
// PUT //test.com/api/abc/xyz
// BODY { "title": "winning" }
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).update(
{ group: 'abc', id: 'xyz' },
{ title: 'winning' },
);
Commonly used with Controller.fetch
partialUpdate
- method: 'PATCH'
- path:
path
- schema:
schema
// PATCH //test.com/api/abc/xyz
// BODY { "title": "winning" }
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).partialUpdate(
{ group: 'abc', id: 'xyz' },
{ title: 'winning' },
);
Commonly used with Controller.fetch
delete
- method: 'DELETE'
- path:
path
- schema: new schema.Delete(schema)
- process:
(value, params) {
return value && Object.keys(value).length ? value : params;
},
// DELETE //test.com/api/abc/xyz
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).delete({
group: 'abc',
id: 'xyz',
});
Commonly used with Controller.fetch
Customizing Resources
createResource
builds a great starting point, but often endpoints need to be further customized.
Overriding members with extends() makes this straightforward.
Because we are changing the endpoint types, we must use the {...spread}
pattern rather than assignment.
This is how TypeScript is able to infer the types from our arguments.
const TodoResourceBase = createResource({
path: '/todos/:id',
schema: Todo,
});
export const TodoResource = {
...TodoResourceBase,
getList: TodoResourceBase.getList.extend({
searchParams: {} as { userId?: string | number } | undefined,
}),
}
Explore more Rest Hooks demos
Function Inheritance Patterns
To reuse code around Resource
design, you can create your own function that calls createResource().
This has similar effects as class-based inheritance.
import {
createResource,
RestEndpoint,
type EndpointExtraOptions,
type RestGenerics,
} from '@rest-hooks/rest';
export class AuthdEndpoint<
O extends RestGenerics = any,
> extends RestEndpoint<O> {
getRequestInit(body: any): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'same-origin',
};
}
}
export function createMyResource<U extends string, S extends Schema>({
path,
schema,
Endpoint = AuthdEndpoint,
...extraOptions
}: {
// `readonly` is critical for the argument types to be inferred correctly
readonly path: U;
readonly schema: S;
readonly Endpoint?: typeof RestEndpoint;
urlPrefix?: string;
} & EndpointExtraOptions) {
const BaseResource = createResource({
path,
Endpoint,
schema,
...extraOptions,
});
return {
...BaseResource,
getList: BaseResource.getList.extend({
schema: { results: [schema], total: 0, limit: 0, skip: 0 },
}),
};
}
The Github Example App uses this pattern as well.