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:
- Enable Reinitialize in the Redux Form: Add the
enableReinitialize
option to your form configuration to allow the form to reinitialize wheninitialValues
changes. - Ensure the Data is Fetched: Make sure the data fetching action is completed before rendering the form.
- 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 theinitialValues
prop changes.mapStateToProps
: Maps the Redux state to the form's props, includinginitialValues
from the state.componentDidMount
: ThecomponentDidMount
lifecycle method is used instead ofcomponentWillMount
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.