简体   繁体   中英

Reactjs: Warning: A component is changing a controlled

I am writing a react crud app, my crud is working nice but it has an console error and below this:

Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: 

I tried a lot reading too many thing on stackoverflow, can anyone help me please?

this is my home.js file:

import React from "react"
import Table from "./table"
import Form from "./form"

class Home extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            current: 'SAVE', // button name
            employees: [{name: 'jhon', age: '23', email: 'a@a'}, {name: 'doe', age: '24', email: 'b@a'}],
            currentEmp: {},
            isFormVisible: false
        };
        this.onSubmit = this.onSubmit.bind(this);
        this.onDelete = this.onDelete.bind(this);
        this.setIndex = this.setIndex.bind(this);
    }

    onSubmit(name, age, email, index=null) {
        if(!index && this.state.current == 'SAVE'){
            this.setState({ employees: [...this.state.employees, { name: name, age: age, email: email }] });
        }
        else if(this.state.current == 'Update'){
            var emp = this.state.employees;
            emp[this.state.index].name = name;  //use index from state
            emp[this.state.index].age = age;
            emp[this.state.index].email = email;
            this.setState({
                currentEmp: {},
                employees: emp,
                current: 'SAVE'
            });
        }
        else{
            this.setState({
                currentEmp: {},
                current: 'SAVE',
            });
        }
    };

    setIndex(index){
        var emp = this.state.employees[index];
        emp.index = index;
        this.setState({
            currentEmp: emp,
            current: 'Update',
            index  //set index in state
        });
    }


    // delete employee
    onDelete(event, index) {
        this.setState({
            employees: this.state.employees.filter((item, itemIndex) => (index != itemIndex)),
        });
    };

    render() {
        return (
            <React.Fragment>
              <h1>Employee Information System</h1>

              {this.state.isFormVisible && <div>
              <Form
                currentEmp={this.state.currentEmp}
                submitMe={this.onSubmit}
                currentButtonName={this.state.current} />
              </div>
              }

              <button onClick={() => this.setState({isFormVisible: true})}>ADD NEW</button>

            <hr/>
            <table className="table table-striped table-dark">
                <Table onUpdateTry={this.edit} editThis={this.setIndex} employees={this.state.employees} deleteMe={this.onDelete} />
            </table>
            <p className="test">Ignore this please ! Just showed if sass works or not</p>

            </React.Fragment>
        );
    }
}
export default Home;

and this is my form.js file

import React, { Fragment } from "react"

class Form extends React.Component {
    constructor(props) {
        super(props);
        this.state = {name: '', age: '', email: ''};
        this.onHandleChange = this.onHandleChange.bind(this);
        this.submit = this.submit.bind(this);
    }

    submit(event, name, age, email) {
        if (this.props.submitMe) {
            this.props.submitMe(name, age, email);
        }
        this.setState({name: '', age: '', email: ''}); // clear form after click on submit
    }

    onHandleChange(event) {
        this.setState({
            [event.target.name]: event.target.value
        });
    }
    componentDidUpdate(prevProps){
        if(prevProps.currentEmp != this.props.currentEmp){
            this.setState({
                index: this.props.currentEmp.index,
                name: this.props.currentEmp.name,
                age: this.props.currentEmp.age,
                email: this.props.currentEmp.email,
            });
        }
    }

    render() {
        return (
            <form>
                <div className="form-group">
                    <input onChange={(event) => this.onHandleChange(event)} value={this.state.name} name="name" type="text" />
                </div>

                <div className="form-group">
                    <input onChange={(event) => this.onHandleChange(event)} value={this.state.age} name="age" type="number"/>
                </div>

                <div className="form-group">
                    <input onChange={(event) => this.onHandleChange(event)} value={this.state.email}  name="email" type="text"/>
                </div>

                <button onClick={(event) => this.submit(event, this.state.name, this.state.age, this.state.email)} type="button">{this.props.currentButtonName}</button>

                <button onClick={() => this.setState({isFormVisible: false})}>HIDE ME</button>
            </form>
        );
    }
}

export default Form;

and this is my table.js file:

import React, {Fragment} from "react"


class Table extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            employees: this.props.employees
        };

        //this.onDelete = this.onDelete.bind(this);
        this.onEdit = this.onEdit.bind(this);
    }
    onEdit(event, index){
        if(this.props.editThis){
            this.props.editThis(index);
        }
    }

    render() {
        return (
            <Fragment>
                <thead>
                    <tr>
                    <th scope="col">Name</th>
                    <th scope="col">Age</th>
                    <th scope="col">Email</th>
                    <th scope="col">EDIT</th>
                    <th scope="col">DELETE</th>
                    </tr>
                </thead>
                <tbody>
                {this.props.employees.map((item, index) => (
                    <tr key={index}>
                        <td>{item.name}</td>
                        <td>{item.age}</td>
                        <td>{item.email}</td>
                        <td>
                            <button
                                type="button"
                                onClick={(event) => this.onEdit(event, index)}
                                className="btn btn-primary btn-sm">EDIT
                            </button>
                        </td>
                        <td>
                            <button
                                onClick={(event) => this.props.deleteMe(event, index)}
                                type="button" className="btn btn-danger btn-sm">DELETE
                            </button>
                        </td>
                    </tr>
                ))}
                </tbody>
            </Fragment>
        );
    }
}

export default Table;

The error occurs only when i add something or click on SAVE buttion or update button. Can anyone help me in this case?

Your problem is in your onSubmit method. You are resetting your currentEmp to {} and using it in your Form component. So, when you reset it affects your Form component and all the values become null there. So, you can skip this step maybe?

this.setState({
  employees: emp,
  current: "SAVE"
});

Also, I couldn't look so carefully but probably you are mutating your state directly in many places. For example in the update part.

var emp = this.state.employees;
emp[this.state.index].name = name; //use index from state

This is a mutation. Assigning an object to a new one just do this by reference. So, when you change a property in the new one then it changes the original one. Maybe something like that works:

const emp = this.state.employees.map((el, i) => {
  const { index } = this.state;
  const curEmp = this.state.employees[index];

  if (i !== index) return el;

  return { ...curEmp, name, age, email };
})

Your problem is with this,

currentEmp: {}

You are setting currentEmp to blank object, and in Form component using this object to set state in componentDidUpdate , in result state in Form component is not getting values.

Also don't mutate state directly.

You can set your currentEmp to empty value object, and your state updation should be,

this.setState({
   employees: this.state.employees.map((emp,index) => index === this.state.index ? {name,age,email} : emp),
   current: 'SAVE',
   currentEmp:{name:'',age:'',email:''}
});

Also in your Form component, in submit function you are doing this,

this.setState({name: '', age: '', email: ''});

which is not needed when you are setting currentEmp:{name:'',age:'',email:''} . Your componentDidUpdate method will take care of this.

Demo

Have you tried to read properties in the submit function? Like:

submit() {
    const { name, age, email  } = this.state;
    if (this.props.submitMe) {
        this.props.submitMe(name, age, email);
    }
    this.setState({name: '', age: '', email: ''}); // clear form after click on submit
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM