Skip to main content
Version: 5.0

Using a custom networking library

Resource.fetch() wraps the standard fetch. One key customization is ensuring every network related error thrown has a status member. This is useful in distinguishing code errors from networking errors, and is used in the NetworkManager.

SimpleResource can be used as an abstract class to implement custom fetch methods without including the default.

caution

If you plan on using NetworkErrorBoundary make sure to add a status member to errors, as it catches only errors with a status member.

Fetch (default)

Fetch

The whatwg-fetch polyfill will be useful in environments that don't support it, like node and older browsers (Internet Explorer). Be sure to include it in any bundles that need it.

This implementation is provided as a useful reference for building your own. For the most up-to-date implementation, see the source on master

import type {
SchemaDetail,
SchemaList,
AbstractInstanceType,
} from '@rest-hooks/endpoint';
import { schema } from '@rest-hooks/endpoint';

import BaseResource from './BaseResource.js';
import type { RestEndpoint } from './types.js';

/**
* Represents an entity to be retrieved from a server.
* Typically 1:1 with a url endpoint.
* @see https://resthooks.io/docs/api/resource
*/
export default abstract class Resource extends BaseResource {
/** Endpoint to get a single entity */
static detail<T extends typeof Resource>(
this: T,
): RestEndpoint<
(this: RestEndpoint, params: any) => Promise<any>,
SchemaDetail<AbstractInstanceType<T>>,
undefined
> {
const endpoint = this.endpoint();
return this.memo('#detail', () =>
endpoint.extend({
schema: this,
}),
);
}

/** Endpoint to get a list of entities */
static list<T extends typeof Resource>(
this: T,
): RestEndpoint<
(this: RestEndpoint, params?: any) => Promise<any>,
SchemaList<AbstractInstanceType<T>>,
undefined
> {
const endpoint = this.endpoint();
return this.memo('#list', () =>
endpoint.extend({
schema: [this],
url: this.listUrl.bind(this),
}),
);
}

/** Endpoint to create a new entity (post) */
static create<T extends typeof Resource>(
this: T,
): RestEndpoint<
(this: RestEndpoint, first: any, second?: any) => Promise<any>,
SchemaDetail<AbstractInstanceType<T>>,
true
> {
return this.memo('#create', () => {
const endpoint = this.endpointMutate();
const instanceFetch = this.fetch.bind(this);
return endpoint.extend({
fetch(...args) {
return instanceFetch(
this.url(...args),
this.getFetchInit(args[args.length - 1]),
);
},
url: (...args) => {
return args.length > 1 ? this.listUrl(args[0]) : this.listUrl();
},
schema: this,
});
});
}

/** Endpoint to update an existing entity (put) */
static update<T extends typeof Resource>(
this: T,
): RestEndpoint<
(this: RestEndpoint, params: any, body: any) => Promise<any>,
SchemaDetail<AbstractInstanceType<T>>,
true
> {
const endpoint = this.endpointMutate();
return this.memo('#update', () =>
endpoint.extend({
method: 'PUT',
schema: this,
}),
);
}

/** Endpoint to update a subset of fields of an existing entity (patch) */
static partialUpdate<T extends typeof Resource>(
this: T,
): RestEndpoint<
(this: RestEndpoint, params: any, body: any) => Promise<any>,
SchemaDetail<AbstractInstanceType<T>>,
true
> {
const endpoint = this.endpointMutate();
return this.memo('#partialUpdate', () =>
endpoint.extend({
method: 'PATCH',
schema: this,
}),
);
}

/** Endpoint to delete an entity (delete) */
static delete<T extends typeof Resource>(
this: T,
): RestEndpoint<
(this: RestEndpoint, params: any) => Promise<any>,
schema.Delete<T>,
true
> {
const endpoint = this.endpointMutate();
return this.memo('#delete', () =>
endpoint.extend({
fetch(this: RestEndpoint, params: any) {
return endpoint.fetch
.call(this, params)
.then(res => (res && Object.keys(res).length ? res : params));
},
method: 'DELETE',
schema: new schema.Delete(this),
}),
);
}
}

Superagent

Superagent

import { SimpleResource, Method } from '@rest-hooks/rest';
import type { SuperAgentRequest } from 'superagent';

const ResourceError = `JSON expected but not returned from API`;

/**
* Represents an entity to be retrieved from a server.
* Typically 1:1 with a url endpoint.
*/
export default abstract class Resource extends SimpleResource {
/** A function to mutate all requests for fetch */
static fetchPlugin?: (request: SuperAgentRequest) => SuperAgentRequest;

/** Perform network request and resolve with json body */
static async fetch(
input: RequestInfo, init: RequestInit
) {
let req = request[init.method](input).on('error', () => {});
if (this.fetchPlugin) req = req.use(this.fetchPlugin);
if (init.body) req = req.send(init.body);
return req.then(res => {
if (isInvalidResponse(res)) {
throw new Error(ResourceError);
}
return res.body;
});
}
}

export const isInvalidResponse = (res: request.Response): boolean => {
// Empty is only valid when no response is expect (204)
const resEmptyIsExpected = res.text === '' && res.status === 204;
const resBodyEmpty = Object.keys(res.body).length === 0;
return !(res.type.includes('json') || resEmptyIsExpected) && resBodyEmpty;
};

Axios

Axios

import { SimpleResource, Method } from '@rest-hooks/rest';
import axios from 'axios';

export default abstract class AxiosResource extends SimpleResource {
/** Perform network request and resolve with json body */
static async fetch(
input: RequestInfo, init: RequestInit
) {
return await axios[init.method](input, init.body);
}
}