简体   繁体   中英

How can I dynamically delete components with React

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:

  • using controlled components ( recommended )
  • using uncontrolled components (the one you are using above).

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.

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