In your example setQuestion()
is called as your input is changed i.e. for onChange
event handling. On each call setQuestion()
repeatedly overwrite the question
array of the state
object. So, when the second form data is added to your first element of array then there is no issue, since there is initially no element in the question
array. Hence, your first element of array is constantly overwritten and at last when you click on "Add Question" button then a new element is added to array by addQuestion()
.
The problem is that once you start typing the data in third form the setQuestion()
method is called which updates your state and your question
array is reset. Hence I merged I used ...prevstate.question
to merge the previous state data with the new one.
But hold on this gives rise to another problem. Now you question
array is filled with objects with empty value pairs { questionTitle: "", questionDescription: "" }
each time you change the input value. However, there is no reflect of the current data you are entering in the state object. This is due to the fact that initially the value of all the inputs in your form is empty by default. So, when setQuestion
is called on the change of input the previous objects are now recursively merged with the new objects. This leads to automatic & useless addition of element in array at press of each key. Also lost of new forms are added as a side effect (since you use map()
on this.state.question
to generate new forms)
So, I came up with a trick to overcome this. Just remove the last element of the question
array of this.state
using this.state.questions.splice(-1)
before updating calling this.setState()
to update state information.
See the code below. Here is the demo on codesandbox
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const QuizHead = props => {
return (
<div className="quiz-head">
<h1 className="title">{props.title}</h1>
<p className="description">{props.description}</p>
</div>
);
};
const QuizQuestion = props => {
return (
<div className="quiz-question">
<h2 className="question-title">{props.questionTitle}</h2>
<p className="question-description">{props.questionDescription}</p>
</div>
);
};
class App extends Component {
constructor() {
super();
this.state = {
title: "",
description: "",
questions: []
};
this.setQuestion = this.setQuestion.bind(this);
this.setHead = this.setHead.bind(this);
this.addQuestion = this.addQuestion.bind(this);
}
setHead = event => {
event.preventDefault();
this.setState({
title: this.title.value,
description: this.description.value,
questions: []
});
};
addQuestion = event => {
event.preventDefault();
console.log("addquestion prev state", this.state);
this.setState(prevState => ({
questions: [
...prevState.questions,
{ questionTitle: "", questionDescription: "" }
]
}));
this.state.questions.map((element, i) =>
console.log(element.questionTitle)
);
};
setQuestion(question) {
this.state.questions.splice(-1);
this.setState(prevState => ({
questions: [
...prevState.questions,
{
questionTitle: this.questionTitle.value,
questionDescription: this.questionDescription.value
}
]
}));
console.log("set question", this.state);
}
render() {
return (
<div className="App">
<article className="forms">
<form className="head-form" onChange={this.setHead}>
<input
type="text"
ref={titleInput => {
this.title = titleInput;
}}
/>
<textarea
name="description"
ref={descriptionInput => {
this.description = descriptionInput;
}}
/>
</form>
<section className="questions-section">
{console.log('Form state',this.state)}
{this.state.questions.map((element, i) => (
<form
key={i}
item={element}
className="question-form"
onChange={this.setQuestion}
>
<input
className="question-title"
type="text"
ref={questionTitleInput => {
this.questionTitle = questionTitleInput;
}}
/>
<input
className="question-description"
ref={questionDescriptionInput => {
this.questionDescription = questionDescriptionInput;
}}
type="text"
/>
</form>
))}
<button className="add-question" onClick={this.addQuestion}>
Add Question
</button>
</section>
</article>
<article className="final-quiz">
<QuizHead
title={this.state.title}
description={this.state.description}
/>
{this.state.questions.map((element, i) => (
<QuizQuestion
key={i}
item={element}
questionTitle={element.questionTitle}
questionDescription={element.questionDescription}
/>
))}
</article>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
IMPORTANT NOTE
As per React Documentation a state should be treated as immutable object.
So, it is not appropraite to modify the elements of an array which is present as a property on this.state
. You can reset it directly instead of modifying. However there are other ways to this. You can try Immutability Helpers
You can see this SO post for more detail about what I am saying