Skip to content

gillchristian/remote-data-ts

Repository files navigation

remote-data-ts

Test

Represent fetched data and the statuses it can be in.

Inspired by Elm's krisajenkins/remotedata.

Install

yarn add fp-ts remote-data-ts

npm i fp-ts remote-data-ts

fp-ts is a peer dependency.

Usage

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'));

Edit kx6q84nk5o

API

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;

License

MIT © 2019 Christian Gill