Skip to main content
Version: 6.1

Controller

class Controller {
/*************** Action Dispatchers ***************/
fetch(endpoint, ...args) => ReturnType<E>;
invalidate(endpoint, ...args) => Promise<void>;
resetEntireStore: () => Promise<void>;
receive(endpoint, ...args, response) => Promise<void>;
receiveError(endpoint, ...args, error) => Promise<void>;
subscribe(endpoint, ...args) => Promise<void>;
unsubscribe(endpoint, ...args) => Promise<void>;
/*************** Data Access ***************/
getResponse(endpoint, ...args, state)โ€‹ => { data, expiryStatus, expiresAt };
getError(endpoint, ...args, state)โ€‹ => ErrorTypes | undefined;
}

useController() provides access in React components

fetch(endpoint, ...args)

Fetches the endpoint with given args, updating the Rest Hooks cache with the response or error upon completion.

function CreatePost() {
const { fetch } = useController();

return (
<form
onSubmit={e => fetch(PostResource.create(), {}, new FormData(e.target))}
>
{/* ... */}
</form>
);
}

Endpoint.sideEffect

sideEffect changes the behavior

true

  • Resolves before committing Rest Hooks cache updates.
  • Each call will always cause a new fetch.

undefined

  • Resolves after committing Rest Hooks cache updates.
  • Identical requests are deduplicated globally; allowing only one inflight request at a time.
    • To ensure a new request is started, make sure to abort any existing inflight requests.

useFetchInit()

Overriding Resource.useFetchInit() makes it necessary to hoist all endpoint calls to the function render.

function CreatePost() {
const { fetch } = useController();
// PostResource.create() calls useFetchInit()
const createPost = PostResource.create();

return (
<form
onSubmit={e => fetch(createPost, {}, new FormData(e.target))}
>
{/* ... */}
</form>
);
}

invalidate(endpoint, ...args)

Forces refetching and suspense on useResource with the same Endpoint and parameters.

function ArticleName({ id }: { id: string }) {
const article = useResource(ArticleResource.detail(), { id });
const { invalidate } = useController();

return (
<div>
<h1>{article.title}<h1>
<button onClick={() => invalidate(ArticleResource.detail(), { id })}>Fetch &amp; suspend</button>
</div>
);
}
tip

To refresh while continuing to display stale data - Controller.fetch instead.

Invalidate many endpoints at once

Use schema.Delete to invalidate every endpoint that contains a given entity.

resetEntireStore()

Resets the entire Rest Hooks cache. All inflight requests will not resolve.

This is typically used when logging out or changing authenticated users.

const USER_NUMBER_ONE: string = "1111";

function UserName() {
const user = useResource(CurrentUserResource.detail(), { });
const { resetEntireStore } = useController();

const becomeAdmin = useCallback(() => {
// Changes the current user
impersonateUser(USER_NUMBER_ONE);
resetEntireStore();
}, []);
return (
<div>
<h1>{user.name}<h1>
<button onClick={becomeAdmin}>Be Number One</button>
</div>
);
}

receive(endpoint, ...args, response)

Stores response in cache for given Endpoint and args.

Any components suspending for the given Endpoint and args will resolve.

If data already exists for the given Endpoint and args, it will be updated.

const { receive } = useController();

useEffect(() => {
const websocket = new Websocket(url);

websocket.onmessage = event =>
receive(EndpointLookup[event.endpoint], ...event.args, event.data);

return () => websocket.close();
});

receiveError(endpoint, ...args, error)

Stores the result of Endpoint and args as the error provided.

subscribe(endpoint, ...args)

Marks a new subscription to a given Endpoint. This should increment the subscription.

useSubscription calls this on mount.

This might be useful for custom hooks to sub/unsub based on other factors.

const controller = useController();
const key = endpoint.key(...args);

useEffect(() => {
controller.subscribe(endpoint, ...args);
return () => controller.unsubscribe(endpoint, ...args);
}, [controller, key]);

unsubscribe(endpoint, ...args)

Marks completion of subscription to a given Endpoint. This should decrement the subscription and if the count reaches 0, more updates won't be received automatically.

useSubscription calls this on unmount.

getResponse(endpoint, ...args, state)

returns
{
data: DenormalizeNullable<E['schema']>;
expiryStatus: ExpiryStatus;
expiresAt: number;
}

Gets the (globally referentially stable) response for a given endpoint/args pair from state given.

data

The denormalize response data. Guarantees global referential stability for all members.

expiryStatus

export enum ExpiryStatus {
Invalid = 1,
InvalidIfStale,
Valid,
}

Valid

  • Will never suspend.
  • Might fetch if data is stale

InvalidIfStale

  • Will suspend if data is stale.
  • Might fetch if data is stale

Invalid

  • Will always suspend
  • Will always fetch

expiresAt

A number representing time when it expires. Compare to Date.now().

Example

This is used in useCache, useResource and can be used in Managers to lookup a response with the state provided.

useCache.ts
import {
useController,
StateContext,
EndpointInterface,
} from '@rest-hooks/core';

/** Oversimplified useCache */
function useCache<E extends EntityInterface>(
endpoint: E,
...args: readonly [...Parameters<E>]
) {
const state = useContext(StateContext);
const controller = useController();
return controller.getResponse(endpoint, ...args, state).data;
}
MyManager.ts
import type { Manager, Middleware, actionTypes } from '@rest-hooks/core';
import type { EndpointInterface } from '@rest-hooks/endpoint';

export default class MyManager implements Manager {
protected declare middleware: Middleware;
constructor() {
this.middleware = ({ controller, getState }) => {
return next => async action => {
if (action.type === actionTypes.FETCH_TYPE) {
console.log('The existing response of the requested fetch');
console.log(
controller.getResponse(
action.endpoint,
...(action.meta.args as Parameters<typeof action.endpoint>),
getState(),
).data,
);
}
next(action);
};
};
}

cleanup() {
this.websocket.close();
}

getMiddleware<T extends StreamManager>(this: T) {
return this.middleware;
}
}

getError(endpoint, ...args, state)

Gets the error, if any, for a given endpoint. Returns undefined for no errors.