Skip to content

Instantly share code, notes, and snippets.

@misantronic
Created July 19, 2018 23:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save misantronic/52726a9ee930f88846aa8bd7ffe5d3fa to your computer and use it in GitHub Desktop.
Save misantronic/52726a9ee930f88846aa8bd7ffe5d3fa to your computer and use it in GitHub Desktop.
WrapRequest
import { observable, computed } from 'mobx';
type WrapRequestState = 'loading' | 'fetched' | 'error';
/** @see https://stackoverflow.com/a/4994244/1138860 */
// tslint:disable-next-line:cyclomatic-complexity no-any
function isEmpty(obj: any): boolean {
if (!obj) return true;
if (obj > 0) return false;
if (obj.length > 0) return false;
if (obj.length === 0) return true;
if (typeof obj !== 'object') return true;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) return false;
}
return true;
}
// tslint:disable-next-line:no-any
export class WrapRequest<T = any, U = any, X = any, Y = any, Z = T | X> {
@observable public error?: Error;
@observable private _$!: T | X;
// tslint:disable-next-line:no-any
@observable public transform?: (value: T | X) => Y;
@observable private state?: WrapRequestState;
private req: (params?: U) => Promise<T>;
constructor(
req: (params?: U) => Promise<T>,
$?: T | X,
transform?: ($: T | X) => Y
) {
this.req = req;
this.transform = transform;
if ($) {
this._$ = $;
}
}
public async request(params?: U): Promise<T> {
this.state = undefined;
this.error = undefined;
try {
this.state = 'loading';
const result = await this.req(params);
this._$ = result;
this.state = 'fetched';
} catch (e) {
this.error = e;
this.state = 'error';
}
return this.$ as T;
}
@computed
public get $(): T | X {
if (this.transform) {
// tslint:disable-next-line:no-any
return this.transform(this._$) as any;
}
return this._$;
}
@computed
public get source(): Z {
// tslint:disable-next-line:no-any
return this._$ as any;
}
@computed
public get loading(): boolean {
return this.state === 'loading';
}
public set loading(value: boolean) {
this.state = value ? 'loading' : undefined;
}
@computed
public get fetched(): boolean {
return this.state === 'fetched';
}
public set fetched(value: boolean) {
this.state = value ? 'fetched' : undefined;
}
@computed
public get empty(): boolean {
if (this.fetched && isEmpty(this.$)) {
return true;
}
return false;
}
// tslint:disable-next-line:cyclomatic-complexity
public match(handlers: {
loading?(): React.ReactNode;
fetched?(value: T): React.ReactNode;
empty?(): React.ReactNode;
error?(e: Error): React.ReactNode;
}): React.ReactNode {
if (this.error && handlers.error) {
return handlers.error(this.error);
}
if (this.empty && handlers.empty) {
return handlers.empty();
}
if (this.loading && handlers.loading) {
return handlers.loading();
}
if (this.fetched && handlers.fetched) {
return handlers.fetched(this.$ as T);
}
return null;
}
public reset(value: T): void {
this._$ = value;
}
}
// tslint:disable-next-line:no-any
export function wrapRequest<T = any, U = any, X = undefined>(
request: (params: U) => Promise<T>
): WrapRequest<T, U, X>;
// tslint:disable-next-line:no-any
export function wrapRequest<T = any, U = any, X = T>(
request: (params: U) => Promise<T>,
// tslint:disable-next-line:unified-signatures
defaultData: T
): WrapRequest<T, U, X>;
// tslint:disable-next-line:no-any
export function wrapRequest<T = any, U = any, X = T, Y = any>(
request: (params: U) => Promise<T>,
// tslint:disable-next-line:unified-signatures
defaultData: T,
transform: ($: T | X) => Y
): WrapRequest<Y, U, Y, Y, T>;
/**
* @param request The request to perform when calling `wrapRequest.request`
* @param defaultData set a default value for `wrapRequest.$` e.g. `[]`
* @param transform a function which receives the request `$` and returns a new value
*/
// tslint:disable-next-line:no-any
export function wrapRequest<T = any, U = any, X = any, Y = undefined>(
request: (params?: U) => Promise<T>,
defaultData?: T,
transform?: ($: T | X) => Y
): WrapRequest<T, U> {
return new WrapRequest<T, U, X, Y>(request, defaultData, transform);
}
@misantronic
Copy link
Author

misantronic commented Jul 19, 2018

usage

// basic setup
const users = wrapRequest(() => fetch(`https://api.com/users.json`), []);

// transform result
const userIds = wrapRequest(() => fetch(`https://api.com/users.json`), [], users => users.map(user => user.id));
// request
await users.request();
await userIds.request();

console.log(users.$) // observable array with users-result
console.log(userIds.$) // observable array with transformed users-result
console.log(userIds.source) // observable array with original users-result
// rendering
<div>
    {users.match({
    	loading: () => <div>loading</div>,
    	error: () => <div>error</div>,
    	empty: () => <div>empty</div>,
    	fetched: users => users.map(...)
    })}
</div>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment