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
};
}
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.
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.
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.
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))));
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.
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"]
}
);
Related
I am having some issues with setting the inital form field values using redux-form.
Here is the code I tried
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
firstName: null,
lastName: null,
}
}
componentDidMount() {
this.props.fetchProfile();
}
async handleChange(e) {
await this.setState({ 'initialValues': { [e.target.name] : e.target.value }});
await this.setState({ [e.target.name] : e.target.value });
}
onSubmit = (e) => {
this.props.saveProfile({
firstName: this.state.auth.firstName,
lastName: this.state.auth.lastName,
});
}
componentWillReceiveProps(nextProps) {
this.setState({
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
});
this.setState({ 'initialValues': {
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
}});
}
render() {
return (
<>
<form onSubmit={handleSubmit(this.onSubmit)}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props){
let firstName = '';
let lastName = '';
return {
userData: state.auth.userData,
initialValues:{
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
But it is not setting value in field when load. field is just empty
I guess Redux Form works little different.
You don't need to set explicit onChange handler or declare any state to hold form fields data in Redux Form. Update your code similar to below
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
// No need constructor/to explicitly declare the state
componentDidMount() {
this.props.fetchProfile();
}
render() {
const { handleSubmit, pristine, submitting} = props; // You can have custom handleSubmit too
return (
<>
<form onSubmit={handleSubmit}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props) {
return {
userData: state.auth.userData,
initialValues:{ // These are the initial values. You can give any name here for testing to verify the ReduxForm working
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
The example in question and the answer would definitely help you to understand things better.
I have a component that represents a form for entering book data, e.g. title/author/etc.
If an ID is present, the component will make an API call to the API server, and get the book data.
What I'm trying to accomplish basically, is getting the record from the API server, and then setting the form fields to those values, so that the form is populated with data for the user to edit.
I have a method called loadBook which makes an API call to get the book data. The method works, it gets the record, it sets the state, but the form inputs do not seem to pick up that the state has changed.
What do I need to do to get the form populated with the record that was just fetched?
import React from "react";
import Axios from "axios";
import {
Redirect
} from "react-router-dom";
import FormBase from "../FormBase";
export default class BookForm extends FormBase {
constructor(props) {
super(props);
this.state = {
formFields: {
title: '',
author_id: null,
cover_image: null
},
authors: [],
};
}
componentDidMount() {
this.loadAuthors();
if (this.props.id) {
this.loadBook()
}
}
loadBook = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/books/${this.props.id}`).then(response => {
this.setState(prevState => {
let formFields = Object.assign({}, prevState.formFields)
formFields['title'] = response.data['title']
formFields['author_id'] = response.data['author_id']
return {formFields}
})
})
}
loadAuthors = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/authors`).then(response => {
this.setState({authors: response.data})
})
}
render() {
let authors = this.state.authors.map(author => {
return <option key={author.id} value={author.id}>{author.last_name}, {author.first_name}</option>
})
return (
<form onSubmit={(e) => {e.preventDefault(); this.props.handleSubmit(e, this.state.formFields, true)}}>
{this.state.redirect ? <Redirect to="/admin/books" /> : null}
<div className="form-group">
<label htmlFor="title">Title</label>
<input name="title" value={this.state.title} onChange={this.handleFieldChange} type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="author">Author</label>
<select name="author_id" onChange={this.handleFieldChange} className="custom-select" size="5">
{authors}
</select>
</div>
<div className="custom-file form-group">
<input name="cover_image" type="file" onChange={this.handleFieldChange} className="custom-file-input" id="customFile" />
<label className="custom-file-label" htmlFor="customFile">Cover Image</label>
</div>
<button style={{marginTop: '1rem'}} type="submit" className="btn btn-primary">Submit</button>
</form>
)
}
}
Try setting your state simply like so:
this.setState({formFields:
{
...this.state.formFields,
title: response.data['title'],
author_id: response.data['author_id']
}
})
I essentially followed this guide on uncontrolled components.
I added attributes for each form field using React.createRef(), and then on the form inputs you link the ref object like ref={this.author_id}. Then, you can do this.author_id.current.value = response.data.author_id and the input's value will then be set. This won't trigger onChange though, so you'll need to update the state too.
import React from "react";
import Axios from "axios";
import {
Redirect
} from "react-router-dom";
import FormBase from "../FormBase";
export default class BookForm extends FormBase {
constructor(props) {
super(props);
this.state = {
formFields: {
title: '',
author_id: null,
cover_image: null
},
authors: [],
};
this.title = React.createRef();
this.author_id = React.createRef();
}
componentDidMount() {
this.loadAuthors();
if (this.props.id) {
this.loadBook()
}
}
loadBook = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/books/${this.props.id}`).then(response => {
console.log(this.author_id)
this.author_id.current.value = response.data.author_id
this.title.current.value = response.data.title
this.setState(prevState => {
let formFields = Object.assign({}, prevState.formFields)
formFields['title'] = response.data['title']
formFields['author_id'] = response.data['author_id']
return {formFields}
})
})
}
loadAuthors = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/authors`).then(response => {
this.setState({authors: response.data})
})
}
render() {
let authors = this.state.authors.map(author => {
return <option key={author.id} value={author.id}>{author.last_name}, {author.first_name}</option>
})
return (
<form onSubmit={(e) => {e.preventDefault(); this.props.handleSubmit(e, this.state.formFields, true)}}>
{this.state.redirect ? <Redirect to="/admin/books" /> : null}
<div className="form-group">
<label htmlFor="title">Title</label>
<input name="title" ref={this.title} value={this.state.title} onChange={this.handleFieldChange} type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="author">Author</label>
<select name="author_id" ref={this.author_id} onChange={this.handleFieldChange} className="custom-select" size="5">
{authors}
</select>
</div>
<div className="custom-file form-group">
<input name="cover_image" type="file" onChange={this.handleFieldChange} className="custom-file-input" id="customFile" />
I created my own web-app of posts where you able to add,delete and update posts. Right now when I try to add a post and pass the inputs value throw a function and then execute 'setState' I get an error, something is wrong in the way I do it. could you help out?
I could just remove the root object from the state ('post') and just use the title,body as for themselfs. But I want to structure it this way: Post:{title,body}
import React, { Component } from 'react';
export class addPost extends Component {
state = {
post: {
title: '',
body: ''
}
};
handleChange = e => {
this.setState({ post[e.currentTarget.id]: e.currentTarget.value });
console.log(this.state);
};
handleSubmit = () => {};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
Enter title:
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
id="title"
/>
Enter body:
<input
type="text"
value={this.state.body}
onChange={this.handleChange}
id="body"
/>
</form>
</div>
);
}
}
export default addPost;
I expect to pass this object post:{title:'sometext',body:'sometext'}
You need to fix on two places
Where you are setting state
Where you are accessing state in input
handleChange = e => {
this.setState(prevState => ({
post: { ...prevState.post, [e.target.id]: e.target.value }
}));
};
<input value={this.state.post.title />
<input value={this.state.post.body} />
Made a sandbox for you: https://codesandbox.io/s/p2w7765j0
Most conventions are to use the name property for your inputs and map them using event.target.name
import React, { Component } from "react";
class AddPost extends Component {
state = {
post: {
title: "",
body: ""
}
};
handleChange = event => {
this.setState(
{
post: {
...this.state.post,
[event.target.name]: event.target.value
}
},
() => console.log(this.state)
);
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
Enter title:
<input
type="text"
value={this.state.post.title}
onChange={this.handleChange}
name="title"
/>
Enter body:
<input
type="text"
value={this.state.post.body}
onChange={this.handleChange}
name="body"
/>
</form>
</div>
);
}
}
export default AddPost;
I have a contact page on which I have a contact form defined like this:
import React from "react";
import { Field, reduxForm } from "redux-form";
import Recaptcha from "react-recaptcha";
const required = value => (value ? undefined : "This field is required.");
const email = value => value && !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? "Invalid email address." : undefined;
const renderInput = ({
input,
label,
type,
meta: { touched, error }
}) => (
<div className="form-group">
<label className="col-sm-2 control-label">{ label }</label>
<div className="col-sm-10">
{ (type == "text" || type == "email") ? <input { ...input } type={ type } /> : <textarea { ...input }></textarea> }
{ touched && ((error && <span className="contact-form-error-message">{ error }</span>)) }
</div>
</div>
);
const captcha = (props) => (
<div className="form-group">
<label className="col-sm-2 control-label"></label>
<div className="col-sm-10">
<Recaptcha render="explicit" onloadCallback={ console.log.bind(this, "reCAPTCHA loaded.") }
sitekey="XXXXXXXXXXXXXXXXX" onChange={props.input.onChange} />
</div>
</div>
);
const ContactForm = props => {
const { handleSubmit, submitting } = props
return (
<form className="form-horizontal" onSubmit={ handleSubmit }>
<Field
name="name"
type="text"
component={ renderInput }
label="Name:"
validate={ required }
/>
<Field
name="email"
type="email"
component={ renderInput }
label="Email:"
validate={ [required, email] }
/>
<Field
name="subject"
type="text"
component={ renderInput }
label="Subject:"
validate={ required }
/>
<Field
name="message"
type="textarea"
component={ renderInput }
label="Message:"
validate={ required }
/>
<Field name="recaptchacode" component={ captcha } />
<div className="form-group">
<label className="col-sm-2 control-label"></label>
<div className="col-sm-10">
<button type="submit" id="contact-form-button" disabled={ submitting }>Send</button>
</div>
</div>
</form>
)
}
export default reduxForm({
form: "ContactForm"
})(ContactForm);
The problem is I cannot seem to get the recaptchacode field in the values object when I click the submit button. How do I bind the value of the Recaptcha component to redux-form so that it puts it in the values object?
And since StackOverflow wants me to add more explanation to this because there's too much code, I am writing this text.
So the answer in short as I have managed to get this thing working. There are two npm packages for implementing recaptcha in react:
react-recaptcha and react-google-recaptcha. You want the second one and not the first one (which was my problem and doesn't work with redux-form) and then you want to follow this tutorial: https://github.com/erikras/redux-form/issues/1880
Hope this helps someone.
Here’s how I integrated Google ReCaptcha with React and redux-forms with Language support. Hope this will help someone.
Versions:
React: 16.5.2
react-google-recaptcha: 1.0.5
react-redux: 5.0.6
redux: 3.7.2
redux-form: 7.2.0
Redux form:
import React from 'react';
import {
reduxForm,
Field,
formValueSelector,
change,
} from 'redux-form';
import { testAction } from ‘/actions';
import { connect } from 'react-redux';
import Captcha from './Captcha';
const validate = values => {
const errors = {};
if (!values.captchaResponse) {
errors.captchaResponse = 'Please validate the captcha.';
}
return errors;
};
let TestForm = (props) => {
const {
handleSubmit,
testAction,
language, //extract language from props/or hard code it in Captcha component
} = props;
return (
<Form onSubmit={ handleSubmit(testAction)}>
<Field component={Input} name=“other_input_name” type="text" required/>
<Field dispatch={props.dispatch} component={Captcha} change={change} language={language} name="captchaResponse"/> {/* Pass redux-forms change and language to the Captcha component*/}
<Button type="submit">{‘Validate’}</Button>
</Form>
);
};
const selector = formValueSelector('testForm');
TestForm = connect(
state => ({
recaptchaValue: selector(state, 'captchaResponse'),
}),
{ testAction: testAction }
)(TestForm);
export default reduxForm({
form: ‘testForm’,
validate,
enableReinitialize: true,
})(TestForm);
Captcha component:
import React, { Component } from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import styled from 'styled-components';
import { change } from 'redux-form';
class Captcha extends Component {
constructor(props) {
super(props);
window.recaptchaOptions = { lang: this.props.language }; //set language that comes from props E.g.: fr/es/en etc..
}
onChange = (value) => {
this.props.meta.dispatch(change(‘testForm’, 'captchaResponse', value));
};
render() {
const { meta: { touched, error } } = this.props;
return (
<CaptchaWrapper>
<ReCAPTCHA
sitekey={‘xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’}
onChange={response => this.onChange(response)}
/>
<ErrorMessage>{ touched ? error : '' }</ErrorMessage>
</CaptchaWrapper>
);
}
}
const CaptchaWrapper = styled.div`
`;
const ErrorMessage = styled.p`
color: red;
`;
export default Captcha;
I am getting this error in my console.
"Form submission canceled because the form is not connected"
after trying to migrate my redux-form from v5 to v6 since we migrated our app to a more recent version of React.
I am not sure what is going wrong here so i figured I could use a second or third pair of eyes.
Here is my "Smart Component"
import React, { PropTypes } from 'react';
import { reduxForm } from 'redux-form/immutable';
import { connect } from 'react-redux';
import { logUserIn } from '../../actions/authentication';
import { VALID_EMAIL_REGEX } from '../../config/app_config';
import LoginForm from './LoginForm';
const FORM_ID = 'loginForm';
export class LoginFormContainer extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
loginAction: PropTypes.func.isRequired,
};
performLogin = (params) => {
const { loginAction } = this.props;
const credentials = {
email: params.email,
password: params.password,
};
loginAction(credentials, '/home');
}
render() {
const { handleSubmit, submitting } = this.props;
return (
<LoginForm
handleSubmit={ handleSubmit }
loginFunction={ this.performLogin }
submitting={ submitting }
/>
);
}
}
const validate = values => {
const errors = {};
if (!values.email || values.email === '') {
errors.email = 'Required';
}
else if (!VALID_EMAIL_REGEX.test(values.email)) {
errors.email = 'Invalid email address';
}
if (!values.password || values.password === '') {
errors.password = 'Required';
}
return errors;
};
LoginFormContainer = reduxForm({
form: FORM_ID,
validate,
})(LoginFormContainer);
export default connect(null, {
loginAction: logUserIn,
})(LoginFormContainer);
I am passing down my submission handler function as a prop to my actual form that contains the Field component for inputs. The loginAction will link to the action for redux to send the values to the backend and redirect to home.
import React, { PropTypes } from 'react';
import { Field } from 'redux-form/immutable';
import { getClassName, checkButtonDisabled } from '../../utils/forms';
import { Link } from 'react-router';
const renderInput = (field) => {
return (
<div className={ getClassName(field.meta.touched, field.meta.error) }>
<input
{...field.input}
className="form-control form-control-success"
type={field.type}
/>
{field.meta.touched &&
field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
);
};
export default class LoginForm extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
loginFunction: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
};
render() {
const {
loginFunction,
submitting } = this.props;
return (
<form onSubmit={ loginFunction.bind(this) }>
<fieldset>
<div>
<label className="form-control-label">
Email address
</label>
<Field
name="email"
component={renderInput}
type="text"
placeholder="example#exampledomain.com"
/>
</div>
<div>
<label className="form-control-label">
Password
</label>
<Field
name="password"
component={renderInput}
type="password"
placeholder="your password"
/>
</div>
</fieldset>
<button
type="submit"
className="btn btn-primary"
disabled={ checkButtonDisabled(submitting) }
>
Log In
</button>
<Link to="/forgot-password">Forgot Password?</Link>
</form>
);
}
}
I successfully was able to get the form to work but when I hit login I get the error above and I am redirected to home, but I am not authenticated and I get a 422 error as well. I couldn't tell if my form connecting is the only error or if my action is not getting the right information from the form submission function.
Got any suggestions?
You are redirected home, because your loginFunction() is fired, but the form is not submitted
There are a couple of things that need to be updated. Your <form> tag must have a corresponding id and it should handle submit by passing your function to redux-form inbuilt submit handler. So you modifying LoginForm class as follows should get the form working
<form id="loginForm" onSubmit={ handleSubmit(this.loginFunction.bind(this)) } >
More about internal redux-form method handleSubmit here: http://redux-form.com/6.5.0/docs/api/Form.md/
Using the answer given to me above I just wanted to clarify what I did to solve the issue.
I grabbed the handleSubmit method that comes from the reduxForm and passed it to the LoginForm as a prop from the container that also retrieved it from the props.
I also imported the Form component from redux-form on the LoginForm component and just simply replaced the normal JSX tag with .
here were the final changes I made.
LoginForm.jsx:
//Added Form from redux-form
import { Field, Form } from 'redux-form/immutable';
render() {
const {
handleSubmit,//defined handleSubmit function from props which comes from the reduxForm being exported to the state.
loginFunction,
submitting } = this.props;
return (
//replaced <form></form> with <Form></Form>
<Form id="loginForm" onSubmit={ handleSubmit(loginFunction.bind(this)) }>
//passed my loginFunction into the handleSubmit function
//added an id to <Form> that corresponds to the forms id that was passed in reduxForm({ form: 'loginForm' })
<fieldset>
<div>
<label className="form-control-label">
Email address
</label>
<Field
name="email"
component={renderInput}
type="text"
placeholder="example#exampledomain.com"
/>
</div>
<div>
<label className="form-control-label">
Password
</label>
<Field
name="password"
component={renderInput}
type="password"
placeholder="your password"
/>
</div>
</fieldset>
<button
type="submit"
className="btn btn-primary"
disabled={ checkButtonDisabled(submitting) }
>
Log In
</button>
<Link to="/forgot-password">Forgot Password?</Link>
</Form>
);