import * as E from 'fp-ts/lib/Either' ; import { absurd , constVoid } from 'fp-ts/lib/function' ; import { pipe } from 'fp-ts/lib/pipeable' ; import * as TE from 'fp-ts/lib/TaskEither' ; import * as t from 'io-ts' ; import { useCallback , useState , useRef , useEffect } from 'react' ; import { HttpError , Schema } from '../types' ; import { Form , useForm } from './useForm' ; type PostError = { type : 'fetch' ; message : string ; } | { type : 'unknownResponse' ; } ; const post = ( url , body ) : TE . TaskEither < PostError , unknown > => TE . tryCatch ( ( ) => fetch ( `/api/${ url }` , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( body ) , } ) . then ( response => response . json ( ) ) , reason => ( { type : 'fetch' , message : String ( reason ) } ) ) ; const handleClientServerMismatch = ( ) => { if ( confirm ( 'Your app is outdated. Please confirm auto update.' ) ) { location . reload ( true ) ; } } ; export const useMutation = < N extends keyof Schema [ 'mutation' ] , M extends typeof Schema [ 'props' ] [ 'mutation' ] [ 'props' ] [ N ] > ( name , initialState , { handleError , handleSuccess , } ) : { form : Form < M [ 'props' ] [ 'input' ] [ 'props' ] > ; isPending : boolean ; } => { const [ isPending , setIsPending ] = useState ( false ) ; const mutation = Schema . props . mutation . props [ name ] ; const inputMutation = mutation . props . input ; const formRef = useRef < Form < M [ 'props' ] [ 'input' ] [ 'props' ] > > ( ) ; const handleApiError = useCallback ( ( error ) => { if ( mutation . props . error . is ( error ) ) { if ( handleError && formRef . current ) handleError ( error , formRef . current ) ; return ; } if ( HttpError . is ( error ) ) { switch ( error . status ) { case 'badRequest' : handleClientServerMismatch ( ) ; break ; case 'forbidden' : alert ( error . status ) ; break ; case 'internalServerError' : alert ( error . status ) ; break ; case 'notFound' : alert ( error . status ) ; break ; case 'unauthorized' : alert ( error . status ) ; break ; default : absurd ( error . status ) ; } } } , [ handleError , mutation . props . error ] ) ; const handlePostError = useCallback ( ( error ) => { switch ( error . type ) { case 'fetch' : alert ( `Please check your network connection. Error message: ${ error . message }` ) ; break ; case 'unknownResponse' : handleClientServerMismatch ( ) ; break ; default : absurd ( error ) ; } } , [ ] ) ; const submit = useCallback ( ( form ) => ( data ) => { if ( isPending ) return ; setIsPending ( true ) ; form . disable ( ) ; post ( name , data ) ( ) . then ( posted => { pipe ( posted , E . chain ( response => { const decoded = Schema . props . mutation . props [ name ] . props . output . decode ( response ) ; return pipe ( decoded , E . mapLeft < t . Errors , PostError > ( ( ) => ( { type : 'unknownResponse' , } ) ) ) ; } ) , E . chain ( ( output ) => pipe ( output , E . fold ( error => E . left ( error ) , payload => E . right ( payload ) ) ) ) , E . fold ( error => { setIsPending ( false ) ; form . enable ( ) ; if ( mutation . props . apiError . is ( error ) ) { handleApiError ( error ) ; return ; } handlePostError ( error ) ; } , payload => { setIsPending ( false ) ; form . enable ( ) ; if ( handleSuccess && formRef . current ) handleSuccess ( payload , formRef . current ) ; } ) ) ; } ) ; } , [ handleApiError , handlePostError , handleSuccess , inputMutation , isPending , mutation . props . apiError , name , ] ) ; const handleSubmit = useCallback ( ( form ) => { pipe ( form . validated , E . fold ( constVoid , submit ( form ) ) ) ; } , [ inputMutation , submit ] ) ; const form = useForm ( inputMutation as any , initialState , { handleSubmit , } ) ; useEffect ( ( ) => { formRef . current = form ; } ) ; return { form , isPending } ; } ;