Bromox
Bromox

Reputation: 697

Dispatch not a function

Objective

Setup a dynamic form controlled by the user using react-redux and revalidate to run validation checks on my form.

Problem:

Since my form is dynamic I need to make dynamic validations. In order to do this my form data needs to be passed in as props to my component, which can be used as a second argument in the validate function from revalidate

My approach

To do this I am waiting until the component is mounted, building the form, passing it to redux, and then will map state to props. As the user adds more rows I will update state and then the component will render. I will use shouldComponentUpdate() to avoid any render loops.

The Error

My error is regarding the dispatch. When I try to run the dispatch (which will pass the form into redux) I get Dispatch is not a function error.

I am not supper comfortable with the connect() as I have to wrap redux with it as well as firebase. This syntax really confuses me.

Question

I believe the issue with with how I am exporting the component where I am using HOC like withFirebase, Redux, and Connect. Somewhere along the way I am losing scope to the connect. Can someone shed light into what it is I am doing wrong?

Component

import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
import { Container, Form, Col, Button } from "react-bootstrap";
import MaterialIcon from '../../material-icon/materialIcon';
import { withFirestore } from "react-redux-firebase";
import { connect } from "react-redux";
import TextInput from "../../forms/textInput";
import { combineValidators, isRequired } from "revalidate";
import { setupStudentForm } from '../../../store/actions/students';


const validate = (values, ownprops) => {
    // Will be passing in validation rules, once form is apssed in with props via mapStateToProps.
}

export class CreateStudentsForm extends Component {
    // Using constrcutor so componentDidMount() can render() cann access variables
    constructor(props) {
        super(props);
        this.state = {
            rows: 2,
        }
        this.formArray = [];
        this.form = null;
    }

    componentDidMount() {
        // Once component is rendered, setup form and send to redux
        for (let i = 1; i !== this.state.rows + 1; i++) {
            let firstNameField = {
                fieldName: `firstName${i}`,
                label: 'First Name',
                required: true,
                type: "text",
            }
            let lastNameField = {
                fieldName: `lastName${i}`,
                label: 'Last Name',
                required: true,
                type: "text",
            }
            this.formArray.push([firstNameField, lastNameField]);
        }
        this.props.setupStudentFormHandler(this.formArray);
    }

    // Ensure we do not get stuck in render loop
    shouldComponentUpdate(nextProps, nextState){
        if(nextProps !== this.props){
            return true
        } else {
            return false
        }
    }

    render() {
        // Allows user to add another row
        const addRow = () => {
            this.setState({
                rows: this.state.rows + 1
            })
        }

        // Map through form array and create template
        if (this.formArray) {
            let form = this.formArray.map((field, index) => {
                return (
                    <Form.Row key={index} className="animated fadeIn">
                        <Col xs={5}>
                            <Form.Group className="mb-0 noValidate">
                                <Field
                                    label={field[0].label}
                                    attempt={this.props.attempt}
                                    name={field[0].fieldName}
                                    type={field[0].type}
                                    component={TextInput}
                                />
                            </Form.Group>
                        </Col>
                        <Col xs={5}>
                            <Form.Group className="mb-0 noValidate">
                                <Field
                                    label={field[1].label}
                                    attempt={this.props.attempt}
                                    name={field[1].fieldName}
                                    type={field[1].type}
                                    component={TextInput}
                                />
                            </Form.Group>
                        </Col>
                        <Col xs={2}>
                            <MaterialIcon icon="delete" className="mt-4" />
                        </Col>
                    </Form.Row>
                )
            })
        }
        return (
            <Container>
                {this.form}
                <Button variant="outline-success" onClick={addRow}>Add Another Student</Button>
            </Container>
        )
    }
}

const mapStateToProps = state => {
    return {
        // Get access to student state which will have form
        studentForm: state.students 
    };
  };
  
  const mapDispatchToProps = dispatch => {
    return {
        //Send formArray to redux
        setupStudentFormHandler: (form) => dispatch(setupStudentForm(form))  
    };
  };


export default withFirestore(
    reduxForm({
        form: "createStudents", validate
    })(connect(
            mapDispatchToProps,
            mapStateToProps
        )(CreateStudentsForm)
    )
);

Upvotes: 0

Views: 178

Answers (1)

Ross Allen
Ross Allen

Reputation: 44900

mapStateToProps is the first argument of connect, mapDispatchToProps is the second. Try swapping the order:

connect(
  mapStateToProps,
  mapDispatchToProps
)(CreateStudentsForm)

Upvotes: 1

Related Questions