Static Land Compliant Remote Data Type in TypeScript
npm install srd


!License
Simple Remote Data (SRD) is a fully static land compliant implementation of the Remote Data type in TypeScript - built with Higer Kinded Types>) (HKT's) inspired by fp-ts and Elm Remote Data.
The idea for using HKT's in TypeScript is based on Lightweight higher-kinded polymorphism.
Static Land CompliantWith yarn
``sh`
yarn add srd
or if you prefer npm
`sh`
npm i srd
SRD supports CJS, UMD and ESM bundle outputs.
The following is a common use case in React. Fetching data async, showing it on screen and handling initial, loading, and error states.
Without SRD, we would need something like this:
`tsx
import React, { useState, useEffect } from 'react'
const App = () => {
const [asked, setAsked] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [data, setData] = useState(null)
useEffect(() => {
setAsked(true)
setLoading(true)
fetch('...')
.then((data) => {
setData(data)
setError(false)
setLoading(false)
})
.catch((err) => {
setData(null)
setError(err)
setLoading(false)
})
}, [])
if (!asked) {
return
if (loading) {
return
if (error) {
return
if (data) {
return
return
That's a lot of code for something we do very often, and highly error prone if we forget to unset/set some state values.
Here's the SRD way - using only 1 state variable instead of 4, preventing any impossible states:
`tsx
import React, { useState, useEffect } from 'react'
import { SRD, notAsked, loading, failure, success } from 'srd'const App = () => {
const [rd, setRd] = useState(notAsked())
useEffect(() => {
setRd(loading())
fetch('...')
.then((data) => setRd(success(data)))
.catch((err) => setRd(failure(err)))
}, [])
return SRD.match({
notAsked: () =>
Empty,
loading: () => Loading...,
failure: (err) => {err},
success: (data) => {data},
}, rd)
}
`That's it! Very easy to use, and 90% of the time that's everything you will need.
$3
SRD works even better with Typescript! Declare your RD type once and have typescript powerfully infer it everywhere! Like magic!
`tsx
import React, { useState, useEffect } from 'react'
import { SRD, RD, notAsked, loading, failure, success } from 'srd'
import { Person, getPerson } from './people'const App = () => {
const [rd, setRd] = useState>(notAsked())
useEffect(() => {
setRd(loading())
getPerson(123)
.then((person) => setRd(success(person)))
.catch((err) => setRd(failure(err)))
}, [])
return SRD.match({
notAsked: () =>
Empty,
loading: () => Loading...,
failure: (msg) => {msg},
success: (person) => {person},
}, rd)
}
`Documentation
SRD comes with many of the Static Land functions that we all know and love. Here is a breakdown of all the supported algebras and utilities:$3
For comparing 2 SRD's to see if they are the same type.
> *Note: This only compares the data types and not the inner value. So
Success(5) != Failure(5) but Success(5) == Success(80).`hs
equals :: (RD e a, RD e b) -> boolean
``ts
import { SRD, success, notAsked } from 'SRD'SRD.equals(success(5), notAsked()) // false
`$3
Allowing the type to be
mapped over by the function provided.`hs
map :: (a -> b, RD e a) -> RD e b
``ts
import { SRD, success, loading } from 'SRD'const double = x => x * 2
const rd1 = success(4)
const rd2 = loading()
SRD.map(double, rd1) // success(8)
SRD.map(double, rd2) // loading()
`$3
Allowing the type to be
bimapped over by the functions provided. Common usecase is for when you need to map and mapFailure in one shot.`hs
bimap :: (e -> b, a -> c, RD e a) -> RD b c
``ts
import { SRD, success, failure } from 'SRD'const double = x => x * 2
const formatErr = err =>
Something went wrong: ${err}
const rd1 = success(4)
const rd2 = failure('404 not found')SRD.bimap(formatErr, double, rd1) // success(8)
SRD.bimap(formatErr, double, rd2) // failure('Something went wrong: 404 not found')
`$3
Apply a function wrapped in a SRD to a value wrapped in a SRD.
`hs
ap :: (RD e (a -> b), RD e a) -> RD e b
``ts
import { SRD, success, failure } from 'SRD'const double = x => x * 2
const formatErr = err =>
Something went wrong: ${err}
const rd1 = success(4)
const rd2 = failure('404 not found')SRD.ap(success(double), rd1)) // success(8)
SRD.ap(success(double), rd2)) // failure('404 not found')
`$3
Always returns a
success with whatever value is passed within.`hs
of :: a -> RD e a
``ts
import { SRD } from 'SRD'SRD.of(4) // success(4)
`$3
Provide a default value to be returned when an
SRD is not a success type.`hs
alt :: (RD e a, RD e a) -> RD e a
``ts
import { SRD, success, loading, notAsked } from 'SRD'SRD.alt(success(4), notAsked()) // success(4)
SRD.alt(success(50), success(4)) // success(4)
SRD.alt(loading(), notAsked()) // loading()
SRD.alt(loading(), success(4)) // success(4)
`$3
Similar to
map but the callback must return another SRD.`hs
chain :: (a -> RD e b, RD e a) -> RD e b
``ts
import { SRD, success, failure, notAsked } from 'SRD'SRD.chain(x => success(x * 2), success(4)) // success(8)
SRD.chain(x => success(x * 2), notAsked()) // notAsked()
SRD.chain(x => failure('failed'), success(4)) // failure('failed')
`$3
Provide a mapper object for each SRD type and whichever type the SRD is - that function will run.
`hs
data Matcher e a ::
{ notAsked :: () -> c
, loading :: () -> c
, failure :: e -> c
, success :: a -> c
}match :: (Matcher e a -> c, RD e a) -> c
``ts
import { SRD, success } from 'SRD'SRD.match({
notAsked: () => 'Empty',
loading: () => 'Loading...',
failure: e =>
Err: ${e},
success: data => My data is ${data}
}, success(4)) // My data is 4
`$3
Similar to
map but instead of running the callback on a success, it calls it on a failure.`hs
mapFailure :: (e -> b, RD e a) -> RD b a
``ts
import { SRD, success, failure } from 'SRD'SRD.mapFailure(x =>
hello ${x}, success(4)) // success(4)
SRD.mapFailure(x => hello ${x}, failure('bob')) // failure('hello bob')
`$3
Similar to
map but takes 2 SRD's instead of one, and if both are a success, the provided callback will be called.`hs
map2 :: (a b -> c, RD e a, RD e b) -> RD e c
``ts
import { SRD, success, failure } from 'SRD'SRD.map2((x, y) => x + y, success(4), success(8)) // success(12)
SRD.map2((x, y) => x + y, failure('bob'), success(8)) // failure('bob')
SRD.map2((x, y) => x + y, success(8), failure('bob')) // failure('bob')
`$3
Similar to
map2 but takes 3 SRD's instead of two, and if all three are a success, the provided callback will be called.`hs
map3 :: (a b c -> d, RD e a, RD e b, RD e c) -> RD e d
``ts
import { SRD, success, failure, notAsked, loading } from 'SRD'const add3 = (x, y, z) = x + y + z
SRD.map3(add3, success(4), success(8), success(10)) // success(22)
SRD.map3(add3, failure('bob'), success(8), notAsked()) // failure('bob')
SRD.map3(add3, success(8), loading(), failure('bob')) // loading()
`$3
Similar to
alt, but unwraps the SRD from it's type and runs the callback on it. If the SRD is a success the inner value is passed to the callback and returned, any other value the default is returned.`hs
unwrap :: (b, a -> b, RD e a) -> b
``ts
import { SRD, success, notAsked, loading } from 'SRD'const double = x => x * 2
SRD.unwrap(6, double, success(8)) // 16
SRD.unwrap(6, double, notAsked()) // 6
SRD.unwrap(6, double, loading()) // 6
`$3
Similar to
unwrap, but takes a default thunk instead of a default value.`hs
unpack :: (() -> b, a -> b, RD e a) -> b
``ts
import { SRD, success, notAsked, loading } from 'SRD'const double = x => x * 2
SRD.unpack(() => 6, double, success(8)) // 16
SRD.unpack(() => 6, double, notAsked()) // 6
SRD.unpack(() => 6, double, loading()) // 6
`$3
Takes a default value and an SRD. If the SRD is a success then the inner value is returned, otherwise the default value is returned.
`hs
withDefault :: (a, RD e a) -> a
``ts
import { SRD, success, notAsked, loading } from 'SRD'SRD.withDefault(4, success(8)) // 8
SRD.withDefault(4, notAsked()) // 4
SRD.withDefault(4, loading()) // 4
``