Redux-Form initial values from

2019-01-23 23:01发布

I'm trying to fill the profile form with data from API. Unfortunately redux-form doesn't want to cooperate with me in this case. For some reason fields stays empty whatever I do.

Setting the fixed values instead of values passed from reducer work well for some reason.

Maybe this is because I'm using redux-promise for API calls inside the action creators? How can I live with it and get rid of this. Here is my form component.

import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import { fetchRoleList, fetchUserData } from '../actions';

class UserEdit extends Component {

    componentWillMount() {
        this.props.fetchRoleList();
        this.props.fetchUserData();
    }

    handleEditProfileFormSubmit(formProps) {
        console.log(formProps);
    }

    getRoleOptions(selected_id) {
        if (!this.props.profile) {
            return <option>No data</option>;
        }

        return this.props.profile.roles.map(role => {
            return <option key={role.role_id} value={role.role_id}>{role.name}</option>;
        });
    }

    renderField(props) {
        const { input, placeholder, label, value, type, meta: { touched, error } } = props;
        return (
        <fieldset className={`form-group ${ (touched && error) ? 'has-error' : '' }`}>
            <label>{label}</label>
            <input className="form-control" {...input} type={type} placeholder={placeholder} />
            {touched && error && <div className="error">{error}</div>}
        </fieldset>
        );
    }

    renderSelect({ input, placeholder, options, label, type, meta: { touched, error } }) {
        return (
        <fieldset className={`form-group ${ (touched && error) ? 'has-error' : '' }`}>
            <label>{label}</label>
            <select className="form-control" {...input}>
                {options}
            </select>
            {touched && error && <div className="error">{error}</div>}
        </fieldset>
        );
    }

    render() {
        const { handleSubmit } = this.props;

        const user = this.props.profile.user;

        return (
        <div> {user ? user.email : ''}
            <form onSubmit={handleSubmit(this.handleEditProfileFormSubmit.bind(this))}>
                <Field name="email" label="Email:" component={this.renderField} type="text" placeholder="email@gmail.com" className="form-control"/>
                <Field name="name" label="Name:"  component={this.renderField} type="text" placeholder="John Doe" className="form-control"/>
                <Field name="role" label="Role:" component={this.renderSelect} type="select" className="form-control" options={this.getRoleOptions()}/>
                <button action="submit" className="btn btn-primary">Edit user</button>

                <Field name="password" label="Password:" component={this.renderField} type="password" className="form-control"/>
                <Field name="passwordConfirm" label="Confirm Password:" component={this.renderField} type="password" className="form-control"/>
                { this.props.errorMessage
                &&  <div className="alert alert-danger">
                        <strong>Oops!</strong> {this.props.errorMessage}
                    </div> }
                <button action="submit" className="btn btn-primary">Sign up!</button>
            </form>
        </div>
        );
    }

}

let InitializeFromStateForm = reduxForm({
    form: 'initializeFromState'
})(UserEdit);

InitializeFromStateForm = connect(
  state => ({
    profile: state.profile,
    initialValues: state.profile.user
  }),
  { fetchRoleList, fetchUserData }
)(InitializeFromStateForm);

export default InitializeFromStateForm;

I do believe action creator will be useful as well:

export function fetchUserData(user_id) {
    user_id = user_id ? user_id : '';

    const authorization = localStorage.getItem('token');
    const request = axios.get(`${ROOT_URL}/user/${user_id}`, {
      headers: { authorization }
    });

    return {
        type: FETCH_USER,
        payload: request
    };
}

5条回答
一夜七次
2楼-- · 2019-01-23 23:25

To set the initialValues it is important to apply the reduxForm() decorator before the connect() decorator from redux. The fields will not populate from the store state if the order of the decorators is inverted.

const FormDecoratedComponent = reduxForm(...)(Component)
const ConnectedAndFormDecoratedComponent = connect(...)(FormDecoratedComponent)

If, in addition to setting the values for the first time, you need to re-populate the form every time the state changes then set enableReinitialize: true

Find a simple example in this answer.

Read the official documentation and full example.

Read about this issue here.

查看更多
萌系小妹纸
3楼-- · 2019-01-23 23:27

For a stateless functional component, you can do it like this:

componentWillMount() {
  this.props.initialize({ discountCodes: ["ABC200", "XYZ500"] });
}

For a class, you can do it like this:

const mapStateToProps = state => (
  {
    initialValues: {
      discountCodes: ["ABC200", "XYZ500"]
  }
);
查看更多
ら.Afraid
4楼-- · 2019-01-23 23:33

If the enableReinitialize : true trick does not work, you can update each field when the initialValues prop changes.

componentWillReceiveProps(nextProps) {
    const { change, initialValues } = this.props
    const values = nextProps.initialValues;

    if(initialValues !== values){
        for (var key in values) {
            if (values.hasOwnProperty(key)) {
                change(key,values[key]);
            }
        }
    }
}

I have never worked with FieldsArray but I assume this would not work here.

查看更多
乱世女痞
5楼-- · 2019-01-23 23:39

So, you're trying:

  • Load API data into the form
  • Update the form just on load (aka. initialValues)

Whilst @FurkanO might work, I think the best approach is to load the form when you got all async data, you can do that by creating a parent component / container:

UserEditLoader.jsx

componentDidMount() {
  // I think this one fits best for your case, otherwise just switch it to
  // componentDidUpdate
  apiCalls();
}
/* api methods here */
render() {
 const { profile } = this.props;
 return (
   {profile && <UserEdit profile={profile} />}
 );
} 

Basically what you should be doing in the UserEditLoader is to execute the API functions and update the state (or props if redux connected). Whenever the profile variable isn't empty (meaning you got the data you were expecting) then mount UserEdit with profile as prop.

查看更多
来,给爷笑一个
6楼-- · 2019-01-23 23:42

You need to add enableReinitialize : true as below.

let InitializeFromStateForm = reduxForm({
    form: 'initializeFromState',
    enableReinitialize : true // this is needed!!
})(UserEdit)

If your initialValues prop gets updated, your form will update too.

查看更多
登录 后发表回答