I appreciate that this question may vouch too long a response, but I am really struggling to get my head around how I can make my React app work. Below is what I am trying to do and what I am stuck with!
I am creating a quiz platform where you can build and play quizzes made by you and your friends. You should be able to add as many questions as you like, and up to four answers per question. Below is a picture highlighting the basic quiz builder which I am currently working on.
So far I am able to create as many questions, and I am able to create as many answers, but what I am really struggling with having delete buttons for questions and answers so that it deletes the corresponding question or answer.
Below is my existing code. I have 3 Components, , , .
Quiz Builder
import React from 'react';
import 'material-icons';
import '../css/addQuestion.css';
import AddTextQuestion from './addTextQuestion.js';
class QuizBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
noQuestions: 0,
};
this.addTextQuestion = this.addTextQuestion.bind(this);
}
addTextQuestion() {
this.setState({
noQuestions: this.state.noQuestions + 1,
});
}
render() {
let children = [];
for(let i = 0; i < this.state.noQuestions; i++) {
children.push(<AddTextQuestion key={i} qNo={i + 1}/>);
}
return (
<div>
<h2 className="builderHeader">Quiz Builder</h2>
<div className="questionDiv" id="newQuestionContainer"></div>
<form className="addQForm">
{children}
</form>
<button type="button" id="addQuestion" onClick={this.addTextQuestion}>
<i className="material-icons">add</i>
Question
</button>
</div>
);
}
}
export default QuizBuilder;
AddTextQuestion
import React from 'react';
import '../css/addQuestion.css';
import AnswerText from './answerText.js';
class AddTextQuestion extends React.Component {
constructor(props) {
super(props);
this.state = {
noAnswers: 0,
};
this.addAnswer = this.addAnswer.bind(this);
this.deleteAnswer = this.deleteAnswer.bind(this);
}
addAnswer() {
this.setState({
noAnswers: this.state.noAnswers + 1,
});
}
deleteAnswer(num) {
delete this.answers[num];
this.setState({
noAnswers: this.state.noAnswers - 1,
});
}
render() {
const answerButton = this.state.noAnswers < 4 ?
<button type="button" onClick={this.addAnswer}><i className="material-icons">add</i>Answer
</button> : <button type="button">Max of 4 answers</button>;
let answers = [];
for(let i = 0; i < this.state.noAnswers; i++) {
answers.push(<div key={i}>
<AnswerText qNo={i + 1}/>
<button type="button" id={`delet_${i}`} onClick={}>X</button>
{console.log(i)}
</div>);
}
return (
<div className="qContainer">
<div className="questionContainer containers">
<h3 className="qNo">Question {this.props.qNo}</h3>
<div className="input">
<label htmlFor="question">Question</label>
<input name="question" type="text" id="question"></input>
</div>
</div>
<div className="answerContainer containers">
{answers}
{answerButton}
</div>
</div>
);
}
}
export default AddTextQuestion;
AnswerText
import React from 'react';
class AnswerText extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="input">
<label htmlFor="answer1">Answer {this.props.qNo}</label>
<input name="answer1 " type="text" id={`answer_${this.props.qNo}`}></input>
<input type="radio" name="answerCheck" id="answerCheck"></input>
</div>
);
}
}
export default AnswerText;
Any help would be most appreciated on how best to tackle this. It's most frustrating!
The issue seems in the way that the state is defined. It's a difficult problem, so the answer is quite long.
First of all, there are two approaches in React to creating forms and collecting the data from them:
We'll stick to uncontrolled components from your example but have a read about controlled components to understand both approaches.
Having only noQuestions: 0
and noAnswers: 0
is not enough to support addition and deletion of any element. You need to use arrays: questions: []
and answers: []
.
Those arrays will have IDs in them. For delete operation each question and each answer needs a unique ID. A simple number would do.
For example, starting with answers: []
state we need to set state to answers: [1]
when add button is clicked and to answers: [1, 2]
when it's clicked again.
Now you can delete answer 1 or answer 2 by filtering it out of the answers
array. For example resulting in answers: [2]
when first answer gets deleted.
Here's the code for AddTextQuestion
class:
class AddTextQuestion extends React.Component {
constructor(props) {
super(props);
this.state = {
answers: [],
}
this.addAnswer = this.addAnswer.bind(this);
this.deleteAnswer = this.deleteAnswer.bind(this);
}
addAnswer() {
const len = this.state.answers.length;
// an ID will be last ID + 1 or 1 if array is empty
const newNum = len ? (this.state.answers[len - 1] + 1) : 1;
this.setState({
// add newNum to answers and save the result to state
answers: this.state.answers.concat(newNum),
})
}
deleteAnswer(num) {
this.setState({
// create new copy of answers without 'num' and save to state
answers: this.state.answers.filter(ans => ans !== num),
})
}
render() {
const answerButton = this.state.answers.length < 4 ? <button type="button" onClick={this.addAnswer}><i className="material-icons">add</i>Answer</button> : <button type="button">Max of 4 answers</button>
let answers = []; // unrelated to this.state.answers
for (let i = 0; i < this.state.answers.length; i++) {
answers.push(<div key={this.state.answers[i]}>
<AnswerText qNo={this.props.qNo} ansNo={i + 1} />
<button
type="button"
onClick={() => this.deleteAnswer(this.state.answers[i])}
>X</button>
</div>);
}
const uniqueId = `question-${this.props.qNo}`;
return (
<div className="qContainer">
<div className="questionContainer containers">
<h3 className="qNo">Question {this.props.qNo}</h3>
<div className="input">
<label htmlFor={uniqueId}>Question</label>
<input name={uniqueId} type="text" id={uniqueId}></input>
</div>
</div>
<div className="answerContainer containers">
{answers}
{answerButton}
</div>
</div>
);
}
}
Here's the code for AddTextQuestion
class with similar state changes:
class QuizBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.addTextQuestion = this.addTextQuestion.bind(this);
this.deleteTextQuestion = this.deleteTextQuestion.bind(this);
}
addTextQuestion() {
const len = this.state.questions.length;
// an ID will be last ID + 1 or 1 if array is empty
const newNum = len ? (this.state.questions[len - 1] + 1) : 1;
this.setState({
questions: this.state.questions.concat(newNum),
});
}
deleteTextQuestion(num) {
this.setState({
questions: this.state.questions.filter(q => q !== num),
})
}
render() {
let children = [];
for (let i = 0; i < this.state.questions.length; i++) {
children.push(<AddTextQuestion key={this.state.questions[i]} qNo={i + 1} />)
}
return (
<form>
<h2 className="builderHeader">Quiz Builder</h2>
<div className="questionDiv" id="newQuestionContainer"></div>
<form className="addQForm">
{children}
</form>
<button type="button" id="addQuestion" onClick={this.addTextQuestion}><i className="material-icons">add</i> Question</button>
</form>
)
}
}
Each element needs to be identified by unique ID so that you can collect the data that was input and save it (for example, but wrapping your application with <form>
tag).
Question 1
[ Text ] (id: question-1)
Answer 1
[ Text ] (id: question-1-answer-1) (radio button) (id: question-1-answer)
Answer 2
[ Text ] (id: question-1-answer-2) (radio button) (id: question-1-answer)
Answer 3
[ Text ] (id: question-1-answer-3) (radio button) (id: question-1-answer)
Question 2
[ Text ] (id: question-2)
Answer 1
[ Text ] (id: question-2-answer-1) (radio button) (id: question-2-answer)
Answer 2
[ Text ] (id: question-2-answer-2) (radio button) (id: question-2-answer)
Answer 3
[ Text ] (id: question-2-answer-3) (radio button) (id: question-2-answer)
So here is new AnswerText
class with qNo
and ansNo
props:
class AnswerText extends React.Component {
render() {
const uniqueId = `question-${this.props.qNo}-answer-${this.props.ansNo}`;
return (
<div className="input">
<label htmlFor={uniqueId}>Answer {this.props.ansNo}</label>
<input name={uniqueId} type="text" id={uniqueId} />
<input type="radio" name={`question-${this.props.qNo}-answer`} value={uniqueId} />
</div>
)
}
}
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.