I create a client side routing with new React Router v6.4 features: action and loader
I use React Query (aka TanStack) for async state menagment and redux for simple state management. And Formik to control form.
How can I return the form to its original state after the Action? I need to reset the password field and so that the form state is no longer draft (this is formik state property - true, if any input differ from initial value.
I use single action file for all actions in /profile location. I did it with 'intent' property of submit button:
index.tsx
const router = createBrowserRouter([
{
path: PATH.HOME,
element: <Root />,
children: [
{
errorElement: <ErrorPage />,
children: [
{
path: PATH.PROFILE,
element: <ProfileLayout />,
action: action(queryClient, store.dispatch),
children: [
{
index: true,
element: <Profile />,
action: action(queryClient, store.dispatch),
loader: userLoader(queryClient),
},
{
path: PATH.ORDERS,
element: <Orders />,
},
],
},
],
},
],
},
]);
profileActions.js
export const action =
(queryClient, dispatch) =>
async ({ request, params }) => {
const formData = await request.formData();
const actionType = formData.get('intent');
switch (actionType) {
case SUBMIT.EDIT_PROFILE: {
const userData = Object.fromEntries(formData);
try {
await UserService.editUser(userData);
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER] });
// stay at the same Edit Profile page
return redirect(PATH.PROFILE);
} catch (err) {
console.log('Edit user error:', err);
}
return null;
}
default: {
throw Request('', { status: 404, statusText: 'Submit action not found' });
}
}
};
My Edit Profile page
export const Profile = () => {
const navigation = useNavigation();
const isSubmitting = navigation.state === 'submitting' || navigation.state === 'loading';
const { queryKey, queryFn } = userQuery();
const { data: user } = useQuery({
queryKey,
queryFn,
});
const { name, email } = user;
const initialValues = {
name,
email,
password: '',
};
const validationSchema = Yup.object({
name: Yup.string()
.min(2, intl.formatMessage({ id: 'form.errors.name.min' }))
.required(intl.formatMessage({ id: 'form.errors.input.required' })),
email: Yup.string()
.email(intl.formatMessage({ id: 'form.errors.email.incorrect' }))
.required(intl.formatMessage({ id: 'form.errors.input.required' })),
password: Yup.string()
.min(3, intl.formatMessage({ id: 'form.errors.password.min' }))
.required(intl.formatMessage({ id: 'form.errors.input.required' })),
});
return (
<Formik initialValues={initialValues} validationSchema={validationSchema}>
{({ errors, isValid, touched, dirty }) => (
<Form method='PATCH' className={clsx(s.form)}>
<Field
name={'name'}
type={'text'}
as={InputElement}
error={touched.name && !!errors.name}
errorText={touched.name && errors.name}
/>
<Field
name={'email'}
type={'email'}
as={InputElement}
error={touched.email && !!errors.email}
errorText={touched.email && errors.email}
/>
<Field
name={'password'}
type={'password'}
as={InputElement}
error={touched.password && !!errors.password}
errorText={touched.password && errors.password}
/>
// buttons visible only if any input differ form initial state
{dirty && (
<>
<FormSubmitBtn
value={SUBMIT.EDIT_PROFILE}
disabled={!isValid || isSubmitting}
extraClass={clsx(s.input_submit)}>
{isSubmitting ? (
<ButtonLoader />
) : (
intl.formatMessage({ id: 'profile.form.submit' })
)}
</FormSubmitBtn>
<Button type='secondary' htmlType='reset' extraClass={clsx(s.input_cancel)}>
{intl.formatMessage({ id: 'profile.form.cancel' })}
</Button>
</>
)}
</Form>
)}
</Formik>
)
I find this youtube tutorial, but it uses imperative approach...