Merge branch 'leander/form-error-handling' into 'main'
fix* form error handling See merge request edb/cnapp!4
This commit is contained in:
commit
0b3c82e255
7 files changed
+68
-29
No files matched your search
+5
-4
@@ -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,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user