React form elements onchange event not working when generated dynamically

Viewed 800

I am trying to generate a list of inputs from data and have they work as controlled inputs to capture their data. The text inputs seems to be getting captured well, but the file input onchange event get the error, "TypeError: _this2.handleFileInputChange is not a function". If I take it out of the arrow function, I do not get an error, but the callback still doesn't work. What do you wizards think?

---------- EDIT --------------

Implemented the suggestion to define the function as arrow function, which was fine, but the file input still does not seem to fire an on change event like the other fields. I'm beginning to think this is a limitation of the component.

Any other ideas? Thank you!

import React from "react";
// import { Link } from "gatsby";
import Layout from "../components/layout";
import {Form,InputGroup,Button} from 'bootstrap-4-react'


function camelize(str) {
  return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) {
    return index === 0 ? word.toLowerCase() : word.toUpperCase();
  }).replace(/\s+/g, '');
}

const formContent = [
    ["Profile Name","Ex. College soccer profile Spring 2020","text"],
    ["Player Name", "Ex. Jose Greer", "text"],
    ["Player Profile Photo", "Ex. Jose Greer", "file"]
]



class ProfileForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      profileName: '',
      playerName: '',
      profileImageFilename: '',
    };
  }

  

  handleFileInputchange = (e) => {
    console.log(e.target.files[0]);
    this.setState({profileImageFilename: e.target.files[0].filename})
  }

  handleInputChange = (e) => {
    console.log(e)
    this.setState({[e.target]: e.target.value})
  }

  handleSubmit = (e) => {
    console.log(e)
    e.preventDefault()
  }

  createFormGroup(i) {
    const title = i[0],
      placeholder = i[1],
      type = i[2]
    const titleCamelCase = camelize(title);
    if (type === "file") {
      return (
        <>
          <label key={titleCamelCase + "-label"} htmlFor={titleCamelCase}>
            Player Profile Image
          </label>
          <InputGroup key={titleCamelCase + "-group"} mb="3">
            <Form.CustomFile
              required
              onChange={this.handleFileInputChange}
              key={titleCamelCase + "-file"}
              id={titleCamelCase}
              accept=".jpg, .jpeg, .png"
            >
              {this.state.profileImageFilename
                ? this.state.profileImageFilename
                : "Choose an image. ( .jpg, .jpeg, .png, .gif )"}
            </Form.CustomFile>
          </InputGroup>
        </>
      );
    }

    return (
      <>
        <label key={titleCamelCase + "-label"} htmlFor={titleCamelCase}>
          {title}
        </label>
        <Form.Input
          key={titleCamelCase + "-input"}
          mb="3"
          required
          type={type}
          id={titleCamelCase}
          placeholder={placeholder}
          onChange={this.handleInputChange}
          valule={this.state[titleCamelCase]}
        />
      </>
    );
  }

  render() {
    return (
      <Layout>
        <Form onSubmit={this.handleSubmit}>
          <Form.Group>
            {/* {formContent.map(function(i){this.createFormGroup(i)}.bind(this))} */}
            {formContent.map((i)=>this.createFormGroup(i))}
          </Form.Group>
          <Button primary type="submit">
            Submit
          </Button>
        </Form>
      </Layout>
    );
  }
}

export default ProfileForm
2 Answers

Issue

The this of the class component isn't bound to the handleFileInputchange callback handler.

Solution

You should bind this to handleFileInputchange in the constructor or declare it as an arrow function.

constructor(props) {
  super(props);
  this.state = {
    profileName: '',
    playerName: '',
    profileImageFilename: '',
  };

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

or

handleFileInputchange = (e) => {
  console.log(e);
  this.setState({ profileImageFilename: e.target.file[0].filename });
}

You can also safely remove the anonymous callback and directly pass this.handleFileInputChange since the onChange event handler and your callback have the same function signature:

onChange={this.handleFileInputChange}

Update

I created a codesandbox with your code. Oddly, I also don't see any onChange callbacks fire (the console logs), and I even added a componentDidUpdate to log state updates and this also doesn't fire. I can add a new file and see a text tooltip with the filename I chose, but nothing updates in the UI. I suggest implementing raw input elements if your the bootstrap inputs are causing issue still.

@drew I ended up having to use a standard input element as I couldn't find any evidence that the react bootstrap component fired a change event. Thank you for your input. You may want to modify as your answer as the correct portion was in your comment.

Related