Represent fetched data and the statuses it can be in.
Inspired by Elm's krisajenkins/remotedata.
yarn add fp-ts remote-data-ts
npm i fp-ts remote-data-ts
fp-ts
is a peer dependency.
RemoteData defines the statuses a fetched data can be:
type RemoteData<Data, Err> = NotAsked | Loading | Success<Data> | Failure<Err>;
import * as React from 'react';
import { render } from 'react-dom';
import { RemoteData, cata } from 'remote-data-ts';
import './styles.css';
interface ArticleProps {
title: string;
body: string;
}
const Article: React.SFC<ArticleProps> = ({ title, body }) => (
<>
<h1>{title}</h1>
<p>{body}</p>
</>
);
type State = RemoteData<ArticleProps, string>;
class App extends React.Component<void, State> {
state = RemoteData.notAsked();
componentDidMount() {
this.setState(RemoteData.loading());
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((res) =>
res.ok ? res.json() : new Error(`${res.status} ${res.statusText}`),
)
.then((article: ArticleProps) => {
this.setState(RemoteData.success(article));
})
.catch((err: Error) => {
this.setState(RemoteData.failure(err.message));
});
}
render() {
return (
<div className="App">
{cata<Article, string, React.ReactNode>({
notAsked: () => null,
loading: () => <div>... loading ...</div>,
success: (article) => <Article {...article} />,
error: (msg) => (
<div className="red">Failed to load article: {msg}</div>
),
})(this.state)}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Create instances:
RemoteData.notAsked(); // NotAsked
RemoteData.loading(); // Loading
RemoteData.of('foo'); // Success<string>
RemoteData.success('foo'); // Success<string>
RemoteData.failure(new Error('failed')); // Failure<Error>
Check status:
type ResolvedData<D, E> = Failure<E> | Success<D>;
const is: {
notAsked: (rd: RemoteData<any, any>) => rd is NotAsked;
loading: (rd: RemoteData<any, any>) => rd is Loading;
success: (rd: RemoteData<any, any>) => rd is Success<any>;
failure: (rd: RemoteData<any, any>) => rd is Failure<any>;
loaded: (rd: RemoteData<any, any>) => rd is ResolvedData<any, any>;
};
Pattern match:
type Match<D, E, R> = {
notAsked: () => R;
loading: () => R;
failure: (error: E) => R;
success: (data: D) => R;
};
type cata = <D, E, R = void>(m: Match<D, E, R>) => (rd: RemoteData<D, E>) => R;
Transform:
type map = <D, E, R>(
fn: (d: D) => R<D, R>,
) => (rd: RemoteData<D, E>) => RemoteData<R, E>;
type chain = <D, E, R>(
fn: (d: D) => RemoteData<R, E>,
) => (rd: RemoteData<D, E>) => RemoteData<R, E>;
type ap = <D, E, R>(
rdfn: RemoteData<(d: D) => R, E>,
) => (rd: RemoteData<D, E>) => RemoteData<R, E>;
type lift2 = <A, B, C, E>(
f: (a: A) => (b: B) => C,
) => (rda: RemoteData<A, E>) => (rdb: RemoteData<B, E>) => RemoteData<C, E>;
Extract:
type fold = <D, E, R>(
fn: (d: D) => R,
) => (def: R) => (rd: RemoteData<D, E>) => R;
MIT © 2019 Christian Gill