Merge branch 'leander/form-error-handling' into 'main'

fix* form error handling

See merge request edb/cnapp!4
This commit is contained in:
leander committed 2024-07-26 01:21:27 +00:00
commit 0b3c82e255
7 files changed
+68 -29

No files matched your search

+5 -4
View File
@@ -79,7 +79,8 @@ export function requestUserData(authToken: string) {
};
}
export function requestRegisterUser({ firstName, lastName, email, phoneNumber, password }: UserRegisterFormValues) {
// We use snake_case in order to fetch and display field errors from the API without any additional formatting
export function requestRegisterUser({ first_name, last_name, email, phone_number, password }: UserRegisterFormValues) {
return (dispatch: AppDispatch) => {
dispatch(registerUserRequest());
@@ -90,10 +91,10 @@ export function requestRegisterUser({ firstName, lastName, email, phoneNumber, p
'Content-Type': 'application/json',
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
first_name: first_name,
last_name: last_name,
email,
phone_number: phoneNumber,
phone_number: phone_number,
password,
}),
})
+11 -4
View File
@@ -4,17 +4,24 @@ import { Text, StyleSheet, View } from 'react-native';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const FormErrors = ({ errors }: { errors: Record<string, any> }) => {
if (!Object.keys(errors).length || !['non_field_errors', 'detail'].some((errorField) => errorField in errors)) {
return null;
if (!errors) {
return;
}
let errorFormatted;
if ('non_field_errors' in errors) {
if (errors instanceof Error) {
// This check might backfire on us as it's too general. We do not wish to display the error message to the user
// Hence we display this generic one
errorFormatted = 'Kunne ikke sende inn skjemaet! Prøv igjen senere...';
} else if ('non_field_errors' in errors) {
errorFormatted = errors.non_field_errors?.[0];
} else if ('detail' in errors) {
errorFormatted = errors.detail;
}
if (!errorFormatted) errorFormatted = 'Kunne ikke sende inn skjemaet, prøv igjen...';
if (!errorFormatted) {
return;
}
return (
<View style={styles.container}>
+2 -1
View File
@@ -9,7 +9,7 @@ import theme from 'src/theme';
import useLogin from './useLogin';
const Login = () => {
const { isLoggingIn, errors, control, setFocus, onSubmit } = useLogin();
const { isLoggingIn, isSubmitted, errors, loginError, control, setFocus, onSubmit } = useLogin();
return (
<ScrollView>
@@ -50,6 +50,7 @@ const Login = () => {
>
<Text>Glemt passord?</Text>
</Button>
{loginError && isSubmitted && <FormErrors errors={loginError} />}
{isLoggingIn && <ActivityIndicator color={theme.colors.neufOrange} />}
</View>
</ScrollView>
+2 -2
View File
@@ -22,7 +22,7 @@ const useLogin = () => {
const {
control,
handleSubmit,
formState: { errors },
formState: { errors, isSubmitted },
setFocus,
} = useForm<LoginFormValues>();
@@ -37,7 +37,7 @@ const useLogin = () => {
}
}, [isAuthenticated, navigation]);
return { onSubmit, isLoggingIn, setFocus, control, errors: { ...errors, ...loginError } };
return { onSubmit, isLoggingIn, isSubmitted, setFocus, control, errors: { ...errors }, loginError };
};
export default useLogin;
+19 -10
View File
@@ -13,8 +13,18 @@ import useUserRegister from './useUserRegister';
const { Toast } = Incubator;
const UserRegister = () => {
const { isRegisteringUser, onSubmit, isValid, control, errors, registerError, setFocus, toast, setToast } =
useUserRegister();
const {
isRegisteringUser,
onSubmit,
isValid,
isSubmitted,
control,
errors,
registerError,
setFocus,
toast,
setToast,
} = useUserRegister();
const isDisabled = isRegisteringUser || !isValid;
return (
@@ -22,15 +32,15 @@ const UserRegister = () => {
<View style={theme.card}>
<ScrollView>
<FormInput
name="firstName"
name="first_name"
label="Fornavn"
control={control}
errors={errors}
autoFocus
onSubmitEditing={() => setFocus('lastName')}
onSubmitEditing={() => setFocus('last_name')}
/>
<FormInput
name="lastName"
name="last_name"
label="Etternavn"
control={control}
errors={errors}
@@ -48,10 +58,10 @@ const UserRegister = () => {
required: 'Kan ikke være tomt!',
validate: (v) => EmailValidator.validate(v) || 'er ikke en gyldig e-post',
}}
onSubmitEditing={() => setFocus('phoneNumber')}
onSubmitEditing={() => setFocus('phone_number')}
/>
<FormInput
name="phoneNumber"
name="phone_number"
label="Mobilnummer"
control={control}
errors={errors}
@@ -70,10 +80,9 @@ const UserRegister = () => {
onSubmitEditing={onSubmit}
/>
<View style={styles.errorBox}>
<FormErrors errors={errors} />
<FormErrors errors={registerError} />
{registerError && isSubmitted && <FormErrors errors={registerError} />}
</View>
<Button fullWidth disabled={isDisabled} onPress={onSubmit} style={styles.registerButton}>
<Button fullWidth disabled={isDisabled} onPress={onSubmit} style={styles.registerButton} type="submit">
<Text>Registrer meg</Text>
</Button>
{isRegisteringUser && <ActivityIndicator color={theme.colors.neufOrange} />}
+28 -7
View File
@@ -7,10 +7,10 @@ import { useAppDispatch, useAppSelector, usePrevious } from 'src/hooks';
import { ToastProps } from 'src/types/toast';
export interface UserRegisterFormValues {
firstName: string;
lastName: string;
first_name: string;
last_name: string;
email: string;
phoneNumber: string;
phone_number: string;
password: string;
}
@@ -19,7 +19,7 @@ const useUserRegister = () => {
const navigation = useNavigation();
const isAuthenticated = useAppSelector((store) => store.isAuthenticated);
const isRegisteringUser = useAppSelector((store) => store.isRegisteringUser);
const registerError = useAppSelector((store) => store.registerError) || {};
const registerError = useAppSelector((store) => store.registerError);
const [toast, setToast] = useState<ToastProps | null>(null);
const isAuthenticatedPrev = usePrevious(isAuthenticated);
@@ -27,11 +27,13 @@ const useUserRegister = () => {
const {
control,
handleSubmit,
formState: { errors, isValid },
formState: { errors, isValid, isSubmitted },
setFocus,
setError,
clearErrors,
} = useForm<UserRegisterFormValues>();
const onSubmit = handleSubmit((values) => {
clearErrors();
if (!isValid) {
setToast({
message: 'Noen av feltene er ikke fylt ut riktig',
@@ -46,7 +48,7 @@ const useUserRegister = () => {
// Display error messages from API on form fields
Object.keys(registerError).forEach((key) => {
setError(key as keyof UserRegisterFormValues, { type: 'manual', message: registerError[key] });
setError(key as keyof UserRegisterFormValues, { type: 'server', message: registerError[key].join('\n') });
});
});
@@ -56,6 +58,25 @@ const useUserRegister = () => {
}
}, [isAuthenticatedPrev, isAuthenticated, navigation]);
return { isRegisteringUser, onSubmit, isValid, registerError, control, errors, setFocus, toast, setToast };
useEffect(() => {
clearErrors();
if (!isSubmitted || !registerError) return;
Object.keys(registerError).forEach((key) => {
setError(key as keyof UserRegisterFormValues, { type: 'server', message: registerError[key].join('\n') });
});
}, [registerError]);
return {
isRegisteringUser,
onSubmit,
isValid,
isSubmitted,
registerError,
control,
errors,
setFocus,
toast,
setToast,
};
};
export default useUserRegister;
+1 -1
View File
@@ -77,7 +77,7 @@ export default function appReducers(state = initialState, action: any) {
return { ...state, isRegisteringUser: true, registerError: null };
case REGISTER_USER_FAILURE:
return { ...state, isRegisteringUser: false, registerError: formatErrors(action.registerError) };
return { ...state, isRegisteringUser: false, registerError: action.registerError };
case REGISTER_USER_SUCCESS:
// eslint-disable-next-line no-case-declarations