Redux-Form initial values from

ghz 6months ago ⋅ 192 views

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
    };
}

Answers

To populate the form fields with data fetched from an API using redux-form and redux-promise, you need to ensure that the data is loaded into the form's initialValues before the form is rendered. The form won't automatically re-initialize when the initialValues prop changes, so you need to use the enableReinitialize option.

Here are the steps to achieve this:

  1. Enable Reinitialize in the Redux Form: Add the enableReinitialize option to your form configuration to allow the form to reinitialize when initialValues changes.
  2. Ensure the Data is Fetched: Make sure the data fetching action is completed before rendering the form.
  3. Connect the Form to Redux: Map the state to props properly to get the initial values from the Redux state.

Here is the updated code with these steps implemented:

UserEdit 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 {

    componentDidMount() {  // Use componentDidMount instead of componentWillMount
        this.props.fetchRoleList();
        this.props.fetchUserData();  // Assuming you pass the user_id somewhere
    }

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

    getRoleOptions = () => {
        if (!this.props.profile || !this.props.profile.roles) {
            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 = ({ input, placeholder, label, type, meta: { touched, error } }) => {
        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, options, label, 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, profile } = this.props;
        const user = profile ? profile.user : null;

        return (
        <div>{user ? user.email : ''}
            <form onSubmit={handleSubmit(this.handleEditProfileFormSubmit)}>
                <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} 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>
        );
    }
}

UserEdit = reduxForm({
    form: 'initializeFromState',
    enableReinitialize: true  // Enable reinitialize
})(UserEdit);

const mapStateToProps = (state) => ({
    profile: state.profile,
    initialValues: state.profile ? state.profile.user : {}
});

export default connect(mapStateToProps, { fetchRoleList, fetchUserData })(UserEdit);

Action Creator

Ensure that your action creator for fetching user data returns a promise so that redux-promise can handle it properly:

import axios from 'axios';
import { FETCH_USER } from './types';  // Make sure to define this constant in your types file

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
    };
}

Reducer

Ensure that your reducer updates the state correctly when the user data is fetched:

import { FETCH_USER } from '../actions/types';

export default function(state = {}, action) {
    switch(action.type) {
        case FETCH_USER:
            return { ...state, user: action.payload.data };
        default:
            return state;
    }
}

Explanation

  • enableReinitialize: true: This option allows the form to reinitialize when the initialValues prop changes.
  • mapStateToProps: Maps the Redux state to the form's props, including initialValues from the state.
  • componentDidMount: The componentDidMount lifecycle method is used instead of componentWillMount to ensure data fetching is complete before rendering the form.

With these changes, your form should correctly populate the fields with data fetched from the API.