Last active
November 30, 2020 13:13
-
-
Save broerjuang/3965c6174e739cf8c88aebd719482b09 to your computer and use it in GitHub Desktop.
Remote Data module similar to ELM remote data in Typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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