0
votes

I am currently building a multi-step form during a user onboarding process, which is why I need to centralize all form data in a parent React component state.

I need to update initialValues with user information but this is an async process.

I thought of creating a useEffect hook calling setState, but maybe there is a more elegant way of doing so...

Having initialValues as one of useEffect dependencies seems to create an infinite loop (Maximum update depth exceeded). This is why the working solution I found was to duplicate all initialValues within... ????

So how could I update only specific values from initialValues after getting async user information?

Here is a simplified version of the implementation:

import React, { useState, useEffect } from 'react'
// Auth0 hook for authentication (via React Context).
import { useAuth0 } from '../../contexts/auth/auth'

import { Formik, Form, Field } from 'formik'

export default () => {
    const { user } = useAuth0()

    const initialValues = {
        profile: {
            name: '',
            address: '',
            // Other properties...
        },
        personalInfo: {
            gender: '',
            birthday: '',
            // Other properties...
        },
    }
    const [formData, setFormData] = useState(initialValues)

    const [step, setStep] = useState(1)
    const nextStep = () => setStep((prev) => prev + 1)

    useEffect(() => {
        const updateInitialValues = (user) => {
            if (user) {
                const { name = '', gender = '' } = user

                const updatedInitialValues = {
                    profile: {
                        name: name,
                        // All other properties duplicated?
                    },
                    personalInfo: {
                        gender: gender,
                        // All other properties duplicated?
                    },
                }

                setFormData(updatedInitialValues)
            }
        }

        updateInitialValues(user)
    }, [user, setFormData])

    switch (step) {
        case 1:
            return (
                <Formik
                    enableReinitialize={true}
                    initialValues={formData}
                    onSubmit={(values) => {
                        setFormData(values)
                        nextStep()
                    }}
                >
                    <Form>
                        <Field name="profile.name" type="text" />
                        <Field name="profile.address" type="text" />
                        {/* Other fields */}
                        <button type="submit">Submit</button>
                    </Form>
                </Formik>
            )
        case 2:
            return (
                <Formik
                    enableReinitialize={true}
                    initialValues={formData}
                    onSubmit={(values) => {
                        setFormData(values)
                        nextStep()
                    }}
                >
                    <Form>
                        <Field name="personalInfo.gender" type="text" />
                        <Field name="personalInfo.birthday" type="text" />
                        {/* Other fields */}
                        <button type="submit">Submit</button>
                    </Form>
                </Formik>
            )
        // Other cases...
        default:
            return <div>...</div>
    }
}
1
already have any solution?Chiien
Unfortunately, not yet... :-/YassineDM

1 Answers

1
votes

it's probably late for me to see this question and I just happen to work on a similar project recently.

For my use case, I'm using only one Formik, and using theory similar to Formik Multistep form Wizard: https://github.com/formium/formik/blob/master/examples/MultistepWizard.js for my multistep forms.

And on each step, I need to fetch API to prefill data, I also use useEffect but since I just call the API onetime when I load the specific step, I force it to behave the same as ComponentDidMount(), which is to leave the [] empty with the comment // eslint-disable-next-line so it won't give me warning.

And I use setFieldValue in the useEffect after data is successfully loaded. I feel mine is also not a good way to handle this situation, and I just found something that might be useful: https://github.com/talor-hammond/formik-react-hooks-multi-step-form, it has a Dynamic initialValues. (Though it's typescript)

I am going to refer to this and also try to use for each of my steps, and probably use Context or Wrap them in a parent and store data in the parent Formik.

And getting infinite loop for you might because setFormData should not be in the dependency, since when you setState, the component re-render, the useEffect calls again.

Not sure if this can help you or you already find out how to implement it, I'll look into this deeper.