map initialValues prop dynamically with nested obj

2019-08-21 09:28发布

问题:

I'm trying to map a redux-form's initialValues property so that the form can be pre-populated (this is a user profile edit screen that is populated from an external API).

Because the validation of what fields the user is allowed to manage occurs on the API, the React JS frontend needs to be as dynamic as possible when it comes to populating the initial values. I did find another question here on SO that addressed dynamically pre-populating initialValues(Dynamically load initialValues in Redux Form), however that solution would only work for objects that have no nesting. Unfortunately, my response from the API does contain some nesting:

{
    "id": 1,
    "email": "admin@test.com",
    "location_id": 2,
    "created_at": "2017-09-08 19:55:01",
    "updated_at": "2017-09-08 19:55:01",
    "can_edit": [ // <-- this array contains all the fields that the given user is allowed to edit on the given profile.
        "profile_img", // <-- possibly this could be rewritten to return for example 'profile.profile_img' rather than just 'profile_img', but not sure how that could help
        "password" // <-- not returned in the actual user object for obvious reasons, but there is logic for displaying the password field anyway
    ],
    "profile": {
        "user_id": 1,
        "first_name": "Admin",
        "last_name": "Test",
        "profile_img": "https://pbs.twimg.com/profile_images/449750767281254400/M_ukevnA.jpeg",
        "position": "Admin",
        "created_at": "2017-09-13 11:38:49",
        "updated_at": "2017-09-13 11:38:49",
    },
    "location": {
        "id": 2,
        "text": "Test Location",
        "lang": "en",
        "created_at": "2017-09-13 11:35:41",
        "updated_at": "2017-09-13 11:35:41",
        "deleted_at": null
    }
}

So I can't simply populate initialValues directly - since, for example, possible editable fields include the email or location_id of the root user object, or the first_name, last_name, or profile_img properties of the nested profile object.

For reference, my EditProfile component is below (truncated to the relevant portions).

class EditProfile extends Component {

    /**
     * Static property types.
     */
    static propTypes = {
        ...
    }

    /**
     * Renders editable profile fields. 
     */
    profileFields(user) {
        if(!this.state.currentField.length){
            this.state.currentField = user.can_edit[0]
        }

        let fields = user.can_edit.map((can_edit) => {
            switch(can_edit){

                // render an email input
                case 'email':
                    return (
                        <ProfileFieldGroup key={can_edit} show={ this.state.currentField == can_edit }>
                            <label htmlFor="email">Email</label>
                            <Field
                                name="email"
                                id="email"
                                type="email"
                                component="input"
                                value={ user.email }
                            />
                        </ProfileFieldGroup>
                    )

                // render a group of password inputs
                case 'password':
                    return (
                        <ProfileFieldGroup key={can_edit} show={ this.state.currentField == can_edit }>
                            <label htmlFor="old_password">Old Password</label>
                            <Field
                                name="old_password"
                                id="old_password"
                                type="password"
                                component="input"
                            />

                            <label htmlFor="password">New Password</label>
                            <Field
                                name="password"
                                id="password"
                                type="password"
                                component="input"
                            />

                            <label htmlFor="password_confirm">Confirm New Password</label>
                            <Field
                                name="password_confirm"
                                id="password_confirm"
                                type="password"
                                component="input"
                            />
                        </ProfileFieldGroup>
                    )

                // by default, create a text input whose ID and name match the given property
                default:
                    console.log(user.profile[can_edit])
                    return (
                        <ProfileFieldGroup key={can_edit} show={ this.state.currentField == can_edit }>
                            <label htmlFor={can_edit}>{ humanizeText(can_edit) }</label>
                            <Field
                                name={ can_edit }
                                id={ can_edit }
                                type="text"
                                component="input"
                                value={ user.profile[can_edit] }
                            />
                        </ProfileFieldGroup>
                    )
            }
        })

        return (
            <div>
                { fields }
            </div>
        )
    }

    /**
     * Handles the submit event.
     * This function is mainly managed by Redux.
     */
    submit = (values) => {
        ...
    }

    /**
     * Renders the component.
     */
    render() {
        const {
            handleSubmit,
            user: user,
            save: save
        } = this.props

        return (
            <section>
                <header>
                    <h1>Edit User</h1>
                </header>

                <div className="main">
                    <form onSubmit={ handleSubmit(this.submit) }>

                        {user.successful && !user.requesting &&
                            this.profileFields(user.user)
                        }

                        {user.requesting && !user.successful && (
                            <img className="loader" src=""/>
                        )}

                    </form>
                </div>

                <footer>

                </footer>
            </section>
        )
    }
}

And then the connection to redux:

const mapStateToProps = state => ({
    user: state.user,
    save: state.save,
    initialValues: state.user.user // <-- this right here
})

const connected = connect(mapStateToProps, { apiGetRequest, 
apiPostRequest })(EditProfile)

const formed = reduxForm({
    form: 'editprofile',
})(connected)

In summary: how can I dynamically populate a redux form's initialValues property from a potentially nested object? Is such a thing even practically possible?

回答1:

how can I dynamically populate a redux form's initialValues property from a potentially nested object? Is such a thing even practically possible?

Yes, it's possible. If you receive nested data from your API, according to the documentation, you can set the keypath in the name prop:

function Form ({ handleSubmit }) {
  return (
    <form onSubmit={handleSubmit}>
      // ...
      <Field name="my.nested.prop" ... />
    </form>
  )
}