Redux-Form initial values from

ReactjsReduxReact ReduxRedux Form

Reactjs Problem Overview


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 protected]" 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
    };
}

Reactjs Solutions


Solution 1 - Reactjs

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.

Solution 2 - Reactjs

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.

Solution 3 - Reactjs

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.

Solution 4 - Reactjs

initialize() is a prop provided by reduxForm, that can be used to fill up the form values.

change() is another prop provided by reduxFrom to change a field value.

import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';

const submit = values => {
	// print the form values to the console
	console.log(values)
}

interface Props {
	history?: any;
	location?: any;
	session?: any;
	handleSubmit?: Function;
	initialize?: Function;
	change?: Function;
}

class ContactForm extends React.Component<Props, any> {
constructor(props, state) {
	super(props, state);
	this.state = {
		value: ''
	};
}

componentDidMount() {
	const { initialize, session, location } = this.props;

	console.log(location.pathname);

	if (session && session.user) {
		const values = {
			firstName: session.user.name,
			lastName: session.user.lastName,
			email: session.user.email
		};
		initialize(values);
	}
}

componentWillReceiveProps(nextProps) {
	const { initialize, session } = this.props;

	if (nextProps.session !== session) {
		if (nextProps.session && nextProps.session.user) {
			const values = {
				firstName: nextProps.session.user.name,
				lastName: nextProps.session.user.lastName,
				email: nextProps.session.user.email
			};
			initialize(values);
		} else {
			const values = {
				firstName: null,
				lastName: null,
				email: null
			};
			initialize(values);
		}
	}
}

render() {
	const { handleSubmit, change } = this.props;
	return (
		<React.Fragment>
			<form onSubmit={handleSubmit(submit)}>
				<div>
					<label htmlFor="firstName">First Name</label>
					<Field name="firstName" component="input" type="text" />
				</div>
				<div>
					<label htmlFor="lastName">Last Name</label>
					<Field name="lastName" component="input" type="text" />
				</div>
				<div>
					<label htmlFor="email">Email</label>
					<Field name="email" component="input" type="email" />
				</div>
				<button type="submit">Submit</button>
			</form>



			<input type="text" value={this.state.value}
				onChange={(e) => {
					this.setState({ value: e.target.value });
					change('firstName', e.target.value);
				}}
			/>
		</React.Fragment>
	);
}
}

export default connect((state) => {
    return {
	    session: state.session
    }
},
{}
)(withRouter((reduxForm({
	form: 'contact'
})(ContactForm))));

Solution 5 - Reactjs

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.

Solution 6 - Reactjs

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"]
  }
);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJ33nnView Question on Stackoverflow
Solution 1 - ReactjsFurkanOView Answer on Stackoverflow
Solution 2 - ReactjsArian AcostaView Answer on Stackoverflow
Solution 3 - ReactjszurfyxView Answer on Stackoverflow
Solution 4 - ReactjsSatyendra SinghView Answer on Stackoverflow
Solution 5 - ReactjsnbeuchatView Answer on Stackoverflow
Solution 6 - ReactjsJustin NoelView Answer on Stackoverflow