Skip to content

Instantly share code, notes, and snippets.

@broerjuang
Last active November 30, 2020 13:13
Show Gist options
  • Save broerjuang/3965c6174e739cf8c88aebd719482b09 to your computer and use it in GitHub Desktop.
Save broerjuang/3965c6174e739cf8c88aebd719482b09 to your computer and use it in GitHub Desktop.
Remote Data module similar to ELM remote data in Typescript
import { useCallback, useState } from "react";
import { isSome, Option, some, none } from "fp-ts/lib/Option";
import { Either, isLeft } from "fp-ts/lib/Either";
type Init = {
readonly _type: "Init";
};
type Loading = {
readonly _type: "Loading";
};
type Success<R> = {
readonly _type: "Success";
readonly value: R;
};
type Failure<L> = {
readonly _type: "Failure";
readonly value: L;
};
export type RemoteData<L, R> = Init | Loading | Success<R> | Failure<L>;
/*
constructor function for creating remote data
*/
let init: RemoteData<never, never> = { _type: "Init" };
let loading: RemoteData<never, never> = { _type: "Loading" };
function success<R>(x: R): RemoteData<never, R> {
return {
_type: "Success",
value: x
};
}
function failure<L>(x: L): RemoteData<L, never> {
return {
_type: "Failure",
value: x
};
}
/*
Utils function to check remote data status
*/
function isInit<L, R>(x: RemoteData<L, R>): boolean {
return x._type === "Init";
}
function isSuccess<L, R>(x: RemoteData<L, R>): boolean {
return x._type === "Success";
}
function isFailure<L, R>(x: RemoteData<L, R>): boolean {
return x._type === "Failure";
}
function isLoading<L, R>(x: RemoteData<L, R>): boolean {
return x._type === "Loading";
}
/*
Lightweight utils
*/
/*
Uncurried version of map
*/
function mapU<L, R, RO>(
fn: (x: R) => RO,
data: RemoteData<L, R>
): RemoteData<L, RO> {
if (data._type === "Success") {
return success(fn(data.value));
} else {
return data;
}
}
/*
inverse version of uncurried map
*/
function mapUI<L, R, RO>(
data: RemoteData<L, R>,
fn: (x: R) => RO
): RemoteData<L, RO> {
return mapU(fn, data);
}
/*
Curried version of map
*/
function map<A, B>(fn: (x: A) => B) {
return <L>(data: RemoteData<L, A>): RemoteData<L, B> => {
if (data._type === "Success") {
return success(fn(data.value));
} else {
return data;
}
};
}
/*
curried version of map and put inverse argument,
taking data first and function leter
*/
function mapI<L, R>(data: RemoteData<L, R>) {
return <O>(fn: (x: R) => O): RemoteData<L, O> => {
return map(fn)(data);
};
}
function mapFailure<L, R, LO>(
fn: (x: L) => LO,
x: RemoteData<L, R>
): RemoteData<LO, R> {
if (x._type === "Failure") {
return failure(fn(x.value));
} else {
return x;
}
}
function mapFailure<A, B>(
fn: (x: A) => B,
x: RemoteData<A, never>
): RemoteData<B, never> {
if (x._type === "Failure") {
return failure(fn(x.value));
} else {
return x;
}
}
function bimap<L, R, LO, RO>(
successFn: (x: R) => RO,
errorFn: (x: L) => LO,
data: RemoteData<L, R>
): RemoteData<LO, RO> {
if (data._type === "Success") {
return success(successFn(data.value));
} else if (data._type === "Failure") {
return failure(errorFn(data.value));
} else {
return data;
}
}
function withDefault<L, R>(defaultValue: R, data: RemoteData<L, R>): R {
if (data._type === "Success") {
return data.value;
} else {
return defaultValue;
}
}
function unwrap<L, R, RO>(
defaultValue: RO,
fn: (x: R) => RO,
data: RemoteData<L, R>
): RO {
if (data._type === "Success") {
return fn(data.value);
} else {
return defaultValue;
}
}
function fromOption<L, R>(err: L, x: Option<R>): RemoteData<L, R> {
if (isSome(x)) {
return success(x.value);
} else {
return failure(err);
}
}
function fromNullable<L, R>(err: L, x: R | null): RemoteData<L, R> {
if (x == null) {
return failure(err);
} else {
return success(x);
}
}
function fromEither<L, R>(x: Either<L, R>): RemoteData<L, R> {
if (isLeft(x)) {
return failure(x.left);
} else {
return success(x.right);
}
}
function toMaybe<L, R>(x: RemoteData<L, R>): Option<R> {
if (x._type === "Success") {
return some(x.value);
} else {
return none;
}
}
/*
Hooks for playing around with remote data,
by passing it as hooks, we can set this using react query etc
*/
function useRemoteData<E, O>() {
let [status, setStatus] = useState<RemoteData<E, O>>(init);
let init_ = useCallback(() => setStatus(init), []);
let loading_ = useCallback(() => setStatus(loading), []);
let failure_ = useCallback((value: E) => setStatus(failure(value)), []);
let success_ = useCallback((value: O) => setStatus(success(value)), []);
return {
status,
init: init_,
loading: loading_,
failure: failure_,
success: success_
};
}
export {
init,
loading,
success,
failure,
useRemoteData,
isInit,
isSuccess,
isFailure,
isLoading,
map,
mapU,
mapI,
mapUI,
mapFailure,
bimap,
withDefault,
unwrap,
fromOption,
fromNullable,
fromEither,
toMaybe
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment