Skip to main content

createResource

Resources are a collection of RestEndpoints that operate on a common data by sharing a schema

Usage

api/TodoResource.ts
export class Todo extends Entity {
id = '';
title = '';
completed = false;
pk() {
return this.id;
}
static key = 'Todo';
}

const TodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
});
Resources start with 6 Endpoints
const todo = useSuspense(TodoResource.get, { id: '5' });
const todos = useSuspense(TodoResource.getList);
controller.fetch(TodoResource.getList.push, {
title: 'finish installing reactive data client',
});
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;
urlPrefix?: string;
body?: any;
searchParams?: any;
paginationField?: string;
optimistic?: boolean;
Endpoint?: typeof RestEndpoint;
} & EndpointExtraOptions

path

Passed to RestEndpoint.path

schema

Passed to RestEndpoint.schema

urlPrefix

Passed to RestEndpoint.urlPrefix

searchParams

Passed to RestEndpoint.searchParams for getList and getList.push

body

Passed to RestEndpoint.body for getList.push update and partialUpdate

optimistic

true makes all mutation endpoints optimistic

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 === '/';
  • schema: new schema.Collection([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

getList.push

  • getList.push
  • method: 'POST'
  • schema: getList.schema.push
// POST //test.com/api/abc
// BODY { "title": "winning" }
createResource({
urlPrefix: '//test.com',
path: '/api/:group/:id',
}).getList.push({ 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.Invalidate(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

extend()

createResource builds a great starting point, but often endpoints need to be further customized.

extend() is polymorphic with three forms:

Batch extension of known members

export const CommentResource = createResource({
path: '/repos/:owner/:repo/issues/comments/:id',
schema: Comment,
}).extend({
getList: { path: '/repos/:owner/:repo/issues/:number/comments' },
update: { body: { body: '' } },
});

Adding new members

export const UserResource = createGithubResource({
path: '/users/:login',
schema: User,
}).extend('current', {
path: '/user',
schema: User,
});

Function form (to get BaseResource/super)

export const IssueResource= createResource({
path: '/repos/:owner/:repo/issues/:number',
schema: Issue,
pollFrequency: 60000,
searchParams: {} as IssueFilters | undefined,
}).extend(BaseResource => ({
search: BaseResource.getList.extend({
path: '/search/issues\\?q=:q?%20repo\\::owner/:repo&page=:page?',
schema: {
results: {
incompleteResults: false,
items: BaseIssueResource.getList.schema.results,
totalCount: 0,
},
link: '',
},
})
)});

Explore more Reactive Data Client 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,
type ResourceGenerics,
type ResourceOptions,
} from '@data-client/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<O extends ResourceGenerics = any>({
schema,
Endpoint = AuthdEndpoint,
...extraOptions
}: Readonly<O> & ResourceOptions) {
return createResource({
Endpoint,
schema,
...extraOptions,
}).extend({
getList: {
schema: {
results: new schema.Collection([schema]),
total: 0,
limit: 0,
skip: 0,
},
},
});
}

Explore more Reactive Data Client demos