Created
June 27, 2023 19:57
-
-
Save emmacv/ecd299649c1f5143b2100ba8a1404c60 to your computer and use it in GitHub Desktop.
hook to handle form actions made in 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 { useCallback, useMemo, useReducer, useRef } from 'react'; | |
import { TextInputProps } from 'react-native'; | |
import useIsMounted from './useIsMounted'; | |
type OnChange = TextInputProps['onChangeText']; | |
type FormValue = Record<string, any>; | |
type Errors<Value extends FormValue> = Partial<Value>; | |
type Settings<Value> = { | |
initialValues: Value; | |
onSubmitForm: (values: Value, helpers?: Record<string, any>) => Promise<void>; | |
validate?: ( | |
values: Value | |
) => Errors<Value> | ((values: Value) => Promise<Errors<Value>>); | |
}; | |
type FormState<Value> = { | |
values: Value; | |
errors: Errors<Value>; | |
touched: Record<keyof Value, boolean>; | |
isSubmitting: boolean; | |
}; | |
type Action = { | |
type: string; | |
payload: any; | |
}; | |
type Reducer = <Value extends FormValue>( | |
state: FormState<Value>, | |
action: Action | |
) => FormState<Value>; | |
const formReducer: Reducer = (state, action) => { | |
const { type, payload } = action; | |
switch (type) { | |
case 'SET_CHANGE': { | |
const { key, value } = <{ key: string; value: string }>payload; | |
return { | |
...state, | |
values: { | |
...state.values, | |
[key]: value, | |
}, | |
touched: { ...state.touched, [key]: true }, | |
errors: { ...state.errors, [key]: undefined }, | |
}; | |
} | |
case 'SET_ERRORS': | |
return { | |
...state, | |
errors: payload, | |
}; | |
case 'SET_SETTLE': | |
return { | |
...state, | |
isSubmitting: false, | |
}; | |
case 'SET_SUBMITTING': | |
return { | |
...state, | |
isSubmitting: true, | |
errors: {}, | |
}; | |
default: | |
return state; | |
} | |
}; | |
const useForm = <Value extends FormValue>(props: Settings<Value>) => { | |
const { validate, onSubmitForm } = props; | |
const initialValues = useRef(props.initialValues).current; | |
const isMounted = useIsMounted(); | |
const [state, dispatch] = useReducer(formReducer, undefined, () => ({ | |
values: initialValues, | |
errors: {}, | |
isSubmitting: false, | |
touched: setTouched(initialValues, false), | |
})); | |
const setErrors = useCallback((errors: Errors<Value>) => { | |
dispatch({ payload: errors, type: 'SET_ERRORS' }); | |
}, []); | |
const helpers = useMemo( | |
() => ({ | |
setErrors, | |
}), | |
[setErrors] | |
); | |
const onChange = useCallback( | |
(key: keyof Value): OnChange => | |
value => { | |
dispatch({ payload: { key, value }, type: 'SET_CHANGE' }); | |
}, | |
[] | |
); | |
const onSubmit = useCallback(async () => { | |
try { | |
dispatch({ payload: null, type: 'SET_SUBMITTING' }); | |
let result = validate(<Value>state.values); | |
if (isPromise(result)) { | |
result = await result; | |
} | |
if (result) { | |
throw result; | |
} | |
await onSubmitForm(<Value>state.values, helpers); | |
} catch (error) { | |
if (!instanceError(error)) { | |
setErrors(error); | |
} else { | |
throw error; | |
} | |
} finally { | |
if (isMounted()) { | |
dispatch({ payload: null, type: 'SET_SETTLE' }); | |
} | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, [state.values, state.errors, validate, onSubmitForm, helpers, setErrors]); | |
const setField = useCallback( | |
(field: keyof Value, value: Value[keyof Value]) => { | |
dispatch({ payload: { key: field, value: value }, type: 'SET_CHANGE' }); | |
}, | |
[] | |
); | |
const setIsSubmitting = useCallback((value: boolean) => { | |
dispatch({ payload: value, type: 'SET_SUBMITTING' }); | |
}, []); | |
return useMemo( | |
() => ({ | |
onChange, | |
values: <Value>state.values, | |
onSubmit, | |
errors: <Record<keyof Value, string>>state.errors, | |
setField, | |
setErrors, | |
setIsSubmitting, | |
isSubmitting: state.isSubmitting, | |
}), | |
[ | |
onChange, | |
state.values, | |
onSubmit, | |
state.errors, | |
setField, | |
setErrors, | |
setIsSubmitting, | |
state.isSubmitting, | |
] | |
); | |
}; | |
export default useForm; | |
const instanceError = (error: any): error is Error => error instanceof Error; | |
const isPromise = (value: any): value is Promise<any> => | |
value instanceof Promise; | |
const setTouched = <Value extends FormValue>( | |
values: Value, | |
value: boolean | |
): Record<keyof Value, boolean> => | |
Object.keys(values).reduce( | |
(acc, key) => ({ ...acc, [key]: value }), | |
{} as Record<keyof Value, boolean> | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment