Skip to content

Instantly share code, notes, and snippets.

@emmacv
Created June 27, 2023 19:57
Show Gist options
  • Save emmacv/ecd299649c1f5143b2100ba8a1404c60 to your computer and use it in GitHub Desktop.
Save emmacv/ecd299649c1f5143b2100ba8a1404c60 to your computer and use it in GitHub Desktop.
hook to handle form actions made in ts.
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