Skip to content

Instantly share code, notes, and snippets.

@pigoz
Created January 31, 2020 14:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pigoz/dadec456fc677b0a2f1b288d03c3a9ca to your computer and use it in GitHub Desktop.
Save pigoz/dadec456fc677b0a2f1b288d03c3a9ca to your computer and use it in GitHub Desktop.
useApi, react, fp-ts
import * as React from 'react';
import * as t from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import * as E from 'fp-ts/lib/Either';
import * as TE from 'fp-ts/lib/TaskEither';
import * as RD from '@devexperts/remote-data-ts';
interface ApiOptions<T> {
token?: string;
method?: string;
path: string;
data?: unknown;
decoder?: t.Decoder<unknown, T>;
}
// ritorna un taskeither, ed è quindi Lazy (ovvero a differenza delle promise
// chiamare questa funzione non fa partire subito la chimata ad api)
export function api<T>(options: ApiOptions<T>): TE.TaskEither<t.Errors, T> {
const decoder = options.decoder || t.any;
const body = JSON.stringify(options.data);
const headers = {
Authorization: `Bearer ${options.token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
};
const lazy = () => {
const response = fetch(`/api${options.path}`, {
method: options.method || 'get',
headers,
body,
});
return response.then(x => x.json());
};
return pipe(
TE.tryCatch(lazy, error => [
{ value: error, context: [], message: 'network error' },
]),
TE.chain(x => TE.fromEither(decoder.decode(x))),
);
}
// react hook per gestire la struttura lazy di cui sopra, scatena la chimata
// ad api e ci ritorna una struttura dati che rappresenta i vari stati di una
// chiamata remota: not started (initial), pending, failure, success.
export function useApi<L, R>(te: TE.TaskEither<L, R>): RD.RemoteData<L, R> {
const [data, setData] = React.useState<RD.RemoteData<L, R>>(RD.initial);
React.useEffect(() => {
setData(RD.pending);
te().then(x => {
setData(
pipe(
x,
E.fold<L, R, RD.RemoteData<L, R>>(RD.failure, RD.success),
),
);
});
}, []);
return data;
}
// codice applicativo
// Decoder a runtime
const Property = t.type({
id: t.string,
address: t.string,
});
// estraiamo il tipo compile-time dal decoder runtime
export type PropetyT = t.TypeOf<typeof Property>;
// componente react che che usa il decoder e le utility functions qui sopra
// per mostrare i dati
export const PropertyComponent = (props: { id: string }) => {
const data = useApi(
api({ path: `/property/${props.id}`, decoder: Property }),
);
return pipe(
data,
RD.fold(
() => <p>api call not started</p>,
() => <p>loading data</p>,
e => <p>error loading api: ${JSON.stringify(e, null, 2)}</p>,
property => <p>{property.address}</p>,
),
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment