Skip to content

Instantly share code, notes, and snippets.

@pigoz
Created January 31, 2020 14:52
Show Gist options
  • Select an option

  • Save pigoz/dadec456fc677b0a2f1b288d03c3a9ca to your computer and use it in GitHub Desktop.

Select an option

Save pigoz/dadec456fc677b0a2f1b288d03c3a9ca to your computer and use it in GitHub Desktop.

Revisions

  1. pigoz created this gist Jan 31, 2020.
    92 changes: 92 additions & 0 deletions network.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    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>,
    ),
    );
    };