Submitting a Redux Form

Viewed 4230

I've got a Redux form which I've attempted to break into several subcomponents as a wizard, like this: https://redux-form.com/7.0.4/examples/wizard/

However, I'm having trouble properly wiring the form into my actions in order to submit the form data. The example actually passes the onSubmit method into the form from the router, which I don't want to do; rather, I'd like to connect my form to my actions, and then pass the signUpUser method into the last of the three components making up the wizard. My current attempt to do so is throwing two errors:

Uncaught TypeError: handleSubmit is not a function

Warning: Failed prop type: The prop `onSubmit` is marked as required in `SignUp`, but its value is `undefined`.

My original form component worked fine, but the same logic does not work in the new component. I think it's a question of scoping, but not sure. I'm relatively new to React and Redux-Form, so am finding this hard to reason through. Ideas?

New (broken) component:

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import SignupFirstPage from './signupComponents/signupFirstPage';
import SignupSecondPage from './signupComponents/signupSecondPage';
import SignupThirdPage from './signupComponents/SignupThirdPage';

class SignUp extends Component {
    constructor(props) {
        super(props);
        this.nextPage = this.nextPage.bind(this);
        this.previousPage = this.previousPage.bind(this);
        this.state = {
            page: 1
        };
        this.handleFormSubmit = this.handleFormSubmit.bind(this);
    }
    nextPage() {
        this.setState({ page: this.state.page + 1 });
    }

    previousPage() {
        this.setState({ page: this.state.page - 1 });
    }

    handleFormSubmit(props) {
        this.props.signUpUser(props);
    }

    render() {
        const { handleSubmit } = this.props;
        const { page } = this.state;
        return (
            <div>
                {page === 1 && <SignupFirstPage onSubmit={this.nextPage} />}
                {page === 2 && (
                    <SignupSecondPage
                        previousPage={this.previousPage}
                        onSubmit={this.nextPage}
                    />
                )}
                {page === 3 && (
                    <SignupThirdPage
                        previousPage={this.previousPage}
                        onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}
                    />
                )}
                <div>
                    {this.props.errorMessage &&
                        this.props.errorMessage.signup && (
                            <div className="error-container">
                                Oops! {this.props.errorMessage.signup}
                            </div>
                        )}
                </div>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        errorMessage: state.auth.error
    };
}

SignUp.propTypes = {
    onSubmit: PropTypes.func.isRequired
};
export default connect(mapStateToProps, actions)(SignUp);

New subcomponent (the final one):

import React from 'react';
import { Field, reduxForm } from 'redux-form';
import validate from './validate';
import renderField from '../../renderField';

const SignupThirdPage = props => {
    const { handleSubmit, pristine, previousPage, submitting } = props;
    return (
        <form onSubmit={handleSubmit}>
            <Field
                name="password"
                type="password"
                component={renderField}
                label="Password"
            />
            <Field
                name="passwordConfirm"
                type="text"
                component={renderField}
                label="Confirm Password"
            />
            <div>
                <button
                    type="button"
                    className="previous btn btn-primary"
                    onClick={previousPage}>
                    Previous
                </button>
                <button
                    className="btn btn-primary"
                    type="submit"
                    disabled={pristine || submitting}>
                    Submit
                </button>
            </div>
        </form>
    );
};
export default reduxForm({
    form: 'wizard', //Form name is same
    destroyOnUnmount: false,
    forceUnregisterOnUnmount: true,
    validate
})(SignupThirdPage);

Old component:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'
import * as actions from '../../actions'
import { Link } from 'react-router';

const renderField = ({ input, label, type, meta: { touched, error, warning } }) => (
    <fieldset className="form-group">
        <label htmlFor={input.name}>{label}</label>
        <input className="form-control" {...input} type={type} />
        {touched && error && <span className="text-danger">{error}</span>}
    </fieldset>
)

class SignUp extends Component {

    constructor(props)
    {
        super(props);

        this.handleFormSubmit = this.handleFormSubmit.bind(this);
    }

    handleFormSubmit(props) {
        // Sign user up
        this.props.signUpUser(props);
    }

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

        return (
            <div className="form-container">
                <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
                    <Field name="firstName" component={renderField} type="text" label="First Name" />
                    <Field name="lastName" component={renderField} type="text" label="Last name" />
                    <Field name="email" component={renderField} type="email" label="Email" />
                    <Field name="company" component={renderField} type="text" label="Company"/>
                    <Field name="password" component={renderField} type="password" label="Password" />
                    <Field name="password_confirmation" component={renderField} type="password" label="Password Confirmation" />
                    <div>
                        {this.props.errorMessage && this.props.errorMessage.signup &&
                            <div className="error-container">Oops! {this.props.errorMessage.signup}</div>}
                    </div>
                    <button type="submit" className="btn btn-primary">Sign Up</button>
                </form>
            </div>
        );
    }
}

function validate(values) {
    let errors = {}

    if (values.password !== values.password_confirmation) {
        errors.password = 'Password and password confirmation don\'t match!'
    }

    return errors
}

function mapStateToProps(state) {
    return {
        errorMessage: state.auth.error
    }
}

SignUp = reduxForm({ form: 'signup', validate })(SignUp);
export default connect(mapStateToProps, actions)(SignUp);
2 Answers

The answer:

Redux-Form says:

You can access your form's input values via the aptly-named values prop provided by the redux-form Instance API.

This is how it works: since my component is connected, I have access to my actions in its props. Therefore, in my render method, I can simply pass the signUpUser method into the subcomponent, like this:

<SignupThirdPage
    previousPage={this.previousPage}
    signingUp={this.signingUp}
    onSubmit={values => this.props.signUpUser(values)}
/>

Your problem is this:

{page === 3 && (
     <SignupThirdPage
       previousPage={this.previousPage}
       onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}
     />
)}

If you carefully look at your code you will find that the function handleSubmit does not exist.

I think you were meaning to write this:

<SignupThirdPage
  previousPage={this.previousPage}
  onSubmit={this.handleFormSubmit.bind(this)}
/>
Related