When i call this function
getQuestions = () => { this.setState({ loading: true }) const { data } = this.props axios .get(data.questions) .then((res) => { this.setState({ loading: false, questions: res.data, }) this.initialQuestions = res.data }) .catch((err) => this.setState({ loading: false, questions: [], }) ) }
it updates the array questions
in state and array initialQuestions
variable in constructor. The state questions
represents the values form inputs. The inputs are handled in child component with this code
onChange = (e) => { const { hasChanged, setQuestions } = this.props // Update questions let questions = this.props.questions questions[e.target.getAttribute('data-id')][e.target.name] = e.target.value setQuestions(questions) }
setQuestions
is passed in props as setQuestions={(state) => this.setState({ questions: state })}
So when i change the value of inputs the onChange
function is called and it changes the parent component questions
in state. But the parent variable this.initialQuestions
also is being changed to the questions
value from state, but I don’t know why
Edit:
That’s the code you should be able to run
const { Component } = React; const Textarea = "textarea"; const objectsEquals = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length && Object.keys(obj1).every((p) => obj1[p] === obj2[p]) class QuestionList extends React.Component { static propTypes = { questions: PropTypes.array, removeQuestion: PropTypes.func.isRequired, hasChanged: PropTypes.func.isRequired, setQuestions: PropTypes.func.isRequired, } constructor(props) { super(props) this.questions = props.questions this.onChange = this.onChange.bind(this) } onChange = (e) => { const { hasChanged, setQuestions } = this.props // Update questions let questions = this.props.questions questions[e.target.getAttribute('data-id')][e.target.name] = e.target.value setQuestions(questions) if (hasChanged && this.questions.length > 0) { // array of booleans, true if object has change otherwise false const hasChangedArray = this.props.questions.map( (_, index) => !objectsEquals( this.questions[index], this.props.questions[index] ) ) console.log("hasChangedArray = ", hasChangedArray) console.log("this.questions[0] = ", this.questions[0]) console.log("this.props.questions[0] = ", this.props.questions[0]) // If true in array than the form has changed hasChanged( hasChangedArray.some((hasChanged) => hasChanged === true) ) } } render() { const { removeQuestion, questions } = this.props const questionList = questions.map((question, index) => ( <div className="card" key={index}> <div className="card__body"> <div className="row"> <div className="col-sm-7"> <div className="form-control"> <label className="form-control__label"> Question: </label> <input type="text" id={`question-${index}`} data-id={index} onChange={this.onChange} name="question" value={ this.props.questions[index].question } className="form-control__input form control__textarea" placeholder="Pass the question..." rows="3" /> </div> <div className="form-control"> <label className="form-control__label"> Summery: </label> <Textarea id={`summery-${index}`} data-id={index} onChange={this.onChange} name="summery" value={this.props.questions[index].summery} className="form-control__input form-control__textarea" placeholder="Pass the summery..." rows="3" /> </div> </div> </div> </div> </div> )) return questionList } } class Questions extends React.Component { constructor(props) { super(props) this.initialQuestions = [] this.state = { loading: true, questions: [], hasChanged: false, } this.getQuestions = this.getQuestions.bind(this) this.resetForm = this.resetForm.bind(this) } resetForm = () => { console.log("this.initialQuestions =", this.initialQuestions) this.setState({ questions: this.initialQuestions, hasChanged: false, }) } getQuestions = () => { this.setState({ loading: true }) const { data } = this.props // axios // .get(data.questions) // .then((res) => { // this.setState({ // loading: false, // questions: res.data, // }) // this.initialQuestions = res.data // }) // .catch((err) => // this.setState({ // loading: false, // questions: [], // }) // ) // You can't do a database request so here is some example code this.setState({ loading: false, questions: [ { question: 'example-question', summery: 'example-summery', }, { question: 'example-question-2', summery: 'example-summery-2', }, ], }) this.initialQuestions = [ { question: 'example-question', summery: 'example-summery', }, { question: 'example-question-2', summery: 'example-summery-2', }, ] } componentDidMount = () => this.getQuestions() render() { const { loading, questions, hasChanged } = this.state if (loading) return <h1>Loading...</h1> return ( <form> <QuestionList questions={questions} hasChanged={(state) => this.setState({ hasChanged: state }) } setQuestions={(state) => this.setState({ questions: state }) } /> <button type="reset" onClick={this.resetForm} className={`btn ${ !hasChanged ? 'btn__disabled' : '' }`} > Cancel </button> <button type="submit" className={`btn btn__contrast ${ !hasChanged ? 'btn__disabled' : '' }`} > Save </button> </form> ) } } ReactDOM.render(<Questions />, document.querySelector("#root"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/prop-types@15/prop-types.min.js"></script> <div id="root"></div>
Advertisement
Answer
Both state questions and class variable initialQuestions hold reference of res.data
. Now when you update questions in onChange
method, you are updating it by reference i.e directly mutating it and hence the class variable is also updated
You must not update it by reference but clone and update like below
onChange = (e) => { const { hasChanged, setQuestions } = this.props // Update questions let questions = this.props.questions questions = questions.map((question, idx) => { if(idx === e.target.getAttribute('data-id')) { return { ...question, [e.target.name]: e.target.value } } return question; }); setQuestions(questions) }