Created
January 31, 2020 14:52
-
-
Save pigoz/dadec456fc677b0a2f1b288d03c3a9ca to your computer and use it in GitHub Desktop.
useApi, react, fp-ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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