5

In ReactJS, in my child component, onChange() event on a input > text just takes one value and not retaining previous values on every keypress.

I am trying to capture the inputs in a child form and wants to transfer it to parent. Actually I am trying to reuse the child form for Create & Edit pages.

My full codesandbox is here https://codesandbox.io/embed/sleepy-stallman-fbyhh?fontsize=14

Child Component

    import React, { Component } from "react";
    import { Form } from "react-bootstrap";

    export default class EmployeeForm extends Component {
      constructor(props) {
        super(props);
        console.log("this.props.employee ", this.props.employee);
      }

      /** Generic handle change events for all fields */
      handleChange = e => {
        this.props.employee[e.target.id] = e.target.value;
        console.log(e.target.value);
      };

      //   handleChange = (key, e) => {
      //     e.preventDefault();
      //     console.log(key);
      //     console.log(e.target.value);
      //     this.props.employee[key] = e.target.value;
      //   };

      render() {
        const { employee } = this.props;
        console.log("ef render ", employee.firstName);

        return (
          <div>
            <Form.Group controlId="firstName">
              <Form.Label>First name</Form.Label>
              <Form.Control
                type="text"
                value={employee.firstName}
                onChange={this.handleChange}
                placeholder="Enter first name"
              />
            </Form.Group>
            <Form.Group controlId="lastname">
              <Form.Label>Last name</Form.Label>
              <Form.Control
                type="text"
                value={employee.lastName}
                onChange={this.handleChange}
                placeholder="Enter last name"
              />
            </Form.Group>
            <Form.Group controlId="birthDate">
              <Form.Label>Date of birth</Form.Label>
              <Form.Control
                type="date"
                value={employee.birthDate}
                onChange={this.handleChange}
              />
            </Form.Group>
            <Form.Group controlId="hireDate">
              <Form.Label>Date of hire</Form.Label>
              <Form.Control
                type="date"
                value={employee.hireDate}
                onChange={this.handleChange}
              />
            </Form.Group>
            <Form.Group controlId="gender">
              <Form.Label>Gender</Form.Label>
              <Form.Control
                as="select"
                value={employee.gender}
                onChange={this.handleChange}
              >
                <option value="">Please select</option>
                <option value="F">Female</option>
                <option value="M">Male</option>
              </Form.Control>
            </Form.Group>
          </div>
        );
      }
    }

Parent Component

    import React from "react";
    import { Alert, Form, Col, Row, Button, Card } from "react-bootstrap";
    import EmployeeForm from "./EmployeeForm";
    import EmployeeService from "./services/EmployeeService";

    export default class CreateEmployee extends React.Component {
      constructor() {
        super();
        this.employeeService = new EmployeeService();
        this.state = {
          employee: {
            firstName: "",
            lastName: "",
            birthDate: "",
            hireDate: "",
            gender: ""
          }
        };
      }

      save = () => {
        console.log(this.state.values);
        this.employeeService
          .createEmployee(this.state.values)
          .then(result => {
            this.setState({ error: null });
          })
          .catch(err => {
            console.log(err);
            this.setState({ error: err });
          });
      };

      render() {
        console.log("reder : ", this.state.employee);

        return (
          <div>
            <Form>
              <Alert variant="primary">Employee</Alert>

              <Card style={{ width: "500px" }}>
                <Card.Header>Create Employee</Card.Header>
                <Card.Body>
                  <EmployeeForm employee={this.state.employee} />
                  <Row>
                    <Col>
                      <Button variant="primary" type="button" onClick={this.save}>
                        Create
                      </Button>
                    </Col>
                  </Row>
                </Card.Body>
              </Card>
            </Form>
          </div>
        );
      }
    }
Jay
  • 9,189
  • 12
  • 56
  • 96
  • 2
    You are mutating state: `this.props.employee[e.target.id] = e.target.value;` pass a change function from parent to child that does a setState without mutating. – HMR Sep 14 '19 at 17:34
  • Thanks, its actual child to parent communication. Once the user enters all data in the child form I want to transfer to the parent. – Jay Sep 14 '19 at 17:40

3 Answers3

2

Your problem is that you have state in your Parent component, and you need to change state in your Parent component from a Child component. In order to achieve this you need to create handlechange method in your Parent component that changes your state and send it with props to your Child component.

Noob
  • 2,247
  • 4
  • 20
  • 31
2

So I went through the code on codesandbox and made the following changes - the obvious changes have comments on-top: You can check them out here - https://codesandbox.io/s/react-parent-child-1fif1?fontsize=14

You should not do the following:

  • Mutate state directly

  • Try to mutate a state in a parent component from child component's props

EmployeeForm.js - Child component

import React, { Component } from "react";
import { Form } from "react-bootstrap";

export default class EmployeeForm extends Component {
  constructor(props) {
    super(props);
  }
// create a handleChangle method here, that calls the handleChange from props
// So you can update the state in CreateEmployee with values from the form

  handleChange = e => {
    this.props.handleChange(e)
  };

  render() {
    const { employee } = this.props;
    // console.log("ef render ", employee.firstName);

    return (
      <div>
        <Form.Group controlId="firstName">
          <Form.Label>First name</Form.Label>
          <Form.Control
            type="text"
            value={employee.firstName}
            onChange={this.handleChange}
            placeholder="Enter first name"
          />
        </Form.Group>
        <Form.Group controlId="lastName">
          <Form.Label>Last name</Form.Label>
          <Form.Control
            type="text"
            value={employee.lastName}
            onChange={this.handleChange}
            placeholder="Enter last name"
          />
        </Form.Group>
        <Form.Group controlId="birthDate">
          <Form.Label>Date of birth</Form.Label>
          <Form.Control
            type="date"
            value={employee.birthDate}
            onChange={this.handleChange}
          />
        </Form.Group>
        <Form.Group controlId="hireDate">
          <Form.Label>Date of hire</Form.Label>
          <Form.Control
            type="date"
            value={employee.hireDate}
            onChange={this.handleChange}
          />
        </Form.Group>
        <Form.Group controlId="gender">
          <Form.Label>Gender</Form.Label>
          <Form.Control
            as="select"
            value={employee.gender}
            onChange={this.handleChange}
          >
            <option value="">Please select</option>
            <option value="F">Female</option>
            <option value="M">Male</option>
          </Form.Control>
        </Form.Group>
      </div>
    );
  }
}

CreateEmployee.js - Parent component

import React from "react";
import { Alert, Form, Col, Row, Button, Card } from "react-bootstrap";
import EmployeeForm from "./EmployeeForm";
import EmployeeService from "./services/EmployeeService";

export default class CreateEmployee extends React.Component {
  constructor() {
    super();
    this.employeeService = new EmployeeService();
    this.state = {
      employee: {
        firstName: "",
        lastName: "",
        birthDate: "",
        hireDate: "",
        gender: ""
      }
    };
  }

  // Create handleChange here and pass it to EmployeeForm as props
  // Use setState instead of mutating state
  handleChange = e => {
    this.setState({employee: {[e.target.id]: e.target.value}})
  };

  save = () => {
    console.log(this.state.values);
    this.employeeService
      .createEmployee(this.state.values)
      .then(result => {
        this.setState({ error: null });
      })
      .catch(err => {
        console.log(err);
        this.setState({ error: err });
      });
  };

  render() {
    console.log("reder : ", this.state.employee);

    return (
      <div>
        <Form>
          <Alert variant="primary">Employee</Alert>

          <Card style={{ width: "500px" }}>
            <Card.Header>Create Employee</Card.Header>
            <Card.Body>
              <EmployeeForm handleChange={this.handleChange} employee={this.state.employee} />
              <Row>
                <Col>
                  <Button variant="primary" type="button" onClick={this.save}>
                    Create
                  </Button>
                </Col>
              </Row>
            </Card.Body>
          </Card>
        </Form>
      </div>
    );
  }
}

Note: I only fixed errors that was required by this question - you might still need to refactor some part of your codes. Don't forget not to mutate the state directly.

Ikechukwu Eze
  • 2,703
  • 1
  • 13
  • 18
1

Here is an example of how you could pass a function from Parent to Child that will use setState to set the state in Parent.

The Parent is a class and the Child is a functional component there are no optimizations (you can prevent creating a new reference for the callback function but that would make the example more complicated):

export default class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      employee: {
        firstName: '',
        lastName: '',
        birthDate: '',
        hireDate: '',
        gender: '',
      },
    };
  }
  inputs = ['lastName'];
  render() {
    return (
      <div>
        {this.inputs.map(key => (
          <Child
            key={key}
            //value from this.state
            value={this.state.employee[key]}
            //will set this.state with value passed
            change={val =>
              this.setState({
                ...this.state,
                employee: {
                  ...this.state.employee,
                  [key]: val,
                },
              })
            }
          />
        ))}
      </div>
    );
  }
}

const Child = ({ change, value }) => {
  const onChange e => change(e.target.value);
  return (
    <input type="text" onChange={onChange} value={value} />
  );
};
HMR
  • 37,593
  • 24
  • 91
  • 160