0

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...

  • Do not Formik forms have a [reset function/method](https://stackoverflow.com/questions/55583815/formik-how-to-reset-form-after-confirmation)? – Drew Reese Jun 21 '23 at 07:46
  • @DrewReese Yes, but then you will need to transfer all the submit logic to the component. And I would like to use the elegant ideology of the new router. The action does all the work with the server and only reports status and data to the component. – Arty Gvozdenkov Jun 21 '23 at 08:02
  • Does this help answer your question? https://stackoverflow.com/questions/66299897/reset-a-react-native-formik-form-from-outside-the-form If so and you still need a bit of help I cant attempt to provide a more up-to-data solution since that post appears to still be using React class components. – Drew Reese Jun 21 '23 at 15:38
  • @DrewReese, Cool, thank you for the idea! Will try with ref and [useNavigation](https://reactrouter.com/en/main/hooks/use-navigation) hook from react router to track mutation status – Arty Gvozdenkov Jun 22 '23 at 15:22
  • 1
    @DrewReese I find more elegant approach - [useFetcher](https://reactrouter.com/en/main/hooks/use-fetcher) from react router. This hook is exactly for my case - make mutation, but not redirect. Only update UI. Will test this later – Arty Gvozdenkov Jun 22 '23 at 16:17

1 Answers1

-2

To reset the form to its original state and remove the draft status after the action, you can utilize the resetForm function provided by Formik. Here's how you can modify your code to achieve that:

  1. Import the useFormik hook from Formik:

    import { Formik, useFormik } from 'formik';

  2. Modify your Profile component to use the useFormik hook and get access to the resetForm function:

    export const Profile = () => {
    // ... other code
    
    const formik = useFormik({
    initialValues,
    validationSchema,
    onSubmit: async (values) => {
      // Your form submission logic goes here
    },
    });
    
    const { errors, isValid, touched, dirty } = formik;
    
    return (
    <FormikProvider value={formik}>
      <Form method='PATCH' className={clsx(s.form)}>
        {/* Form fields */}
      </Form>
    </FormikProvider>
     );
    };
    
  3. In your form submission logic, after the action is successfully completed, call resetForm to reset the form fields to their initial values and remove the draft status:

    const onSubmit = async (values, { resetForm }) => {
    try {
    await UserService.editUser(userData);
    queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER] });
    
    // Reset the form to its initial state
    resetForm();
    
    // stay at the same Edit Profile page
    return redirect(PATH.PROFILE);
    } catch (err) {
    console.log('Edit user error:', err);
    }
    };
    

By using the resetForm function from Formik, you can easily reset the form to its initial state, including the password field, and remove the draft status after the action is performed successfully.

lareb
  • 98
  • 6