2

I want to dynamically add Components, after clicking the "add" button. For that, I created an array that consists of all the components, and add them on click.

My problem is, that it only renders one component, even though it consists of several ones.

My code looks like this:

class QuestionBlock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {answersArray: []};
    }

    addPossibleAnswer() {
        this.state.answersArray.push(
            <PossibleAnswers id={this.state.answersArray.length + 1}/>
        )
        this.forceUpdate();
    }

    componentWillMount() {
        this.state.answersArray.push(
            <PossibleAnswers id={this.state.answersArray.length + 1}/>
        )
    }

    render() {
        console.log(this.state.answersArray) // Grows after adding componenets, but they are not rendered.
        return (
            <div>
                {this.state.answersArray}
                <AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
            </div>
        );
    }
}

If you see what I did wrong, I'd be really glad if you could help me out!

1
  • 2
    Its because you're mutating the state. Commented Nov 3, 2018 at 14:56

3 Answers 3

1

Instead of mutating state directly and adding JSX to it, you can instead keep raw data in your state and derive the JSX from that in the render method instead.

Example

class QuestionBlock extends React.Component {
  state = { answers: 1 };

  addPossibleAnswer = () => {
    this.setState(({ answers }) => ({ answers: answers + 1 }));
  };

  render() {
    return (
      <div>
        {Array.from({ length: this.state.answers }, (_, index) => (
          <PossibleAnswers key={index} id={index} />
        ))}
        <AddPossibleAnswer addPossibleAnswer={this.addPossibleAnswer} />
      </div>
    );
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Hey, thanks for that neat solution! 2 short questions if it's alright. What is the reason to put the _ in the arrow function? I removed it and it worked just as fine. And if I wanted to delete a component by its id, could I somehow filter inside of Array.from(), or would that approach not work then?
@Robin You're welcome! The function that is the second argument of Array.from gets the element and index as arguments, and only the index was used, so I used _ to indicate that it was not used. It will be difficult to remove by id in this particular example since it just loops answers amount of times. If you want to remove by id you will need some more complex data, e.g. state = { answers: [{ id: 3453 }, { id: 4624 }] }
Nice, I understand. Will try that out!
1

You don't interact with state like you do. Never mutate the state field. You need to use this.setState:

this.setState(prevState => ({answersArray: prevState.answersArray.concat([
      <PossibleAnswers id={prevState.answersArray.length + 1}])}));

Having said that, it is also strange that you store components in state. Usually, you would store data and create the components based on the data in the render method.

Comments

1

You are directly pushing elements to the array without setState so the component won't re-render

Also avoid using tthis.forceUpdate() as much as you can in your application because this is not recommended much

You need to change your code like below. The recommended approach for dealing with arrays in react is using previous state and push to an array

addPossibleAnswer() {
       this.setState(prevState => ({
          answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
       }));
    }

componentWillMount() {
    this.setState(prevState => ({
          answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
       }));
}

Also keep in mind that componentWillMount life cycle method is deprecated in react 16. So move the code to componentDidMount instead

Here is the corrected code

class QuestionBlock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {answersArray: []};
    }

    addPossibleAnswer() {
       this.setState(prevState => ({
          answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
       }));
    }

  componentDidMount() {
    this.setState(prevState => ({
          answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
       }));
  }

    render() {
        const { answersArray } = this.state;
        return (
            <div>
                {answersArray}
                <AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
            </div>
        );
    }
}

2 Comments

Hey, also thanks for your solution :) Isn't ComponentWillMount something completely different than ComponentDidMount? I thought I need to use "Will" because it needs to put the data in the array, before it renders
yes componentWillMount gets called before the component but this life cycle method is deprecated and not recommended to use so moving the logic to componentDidMount would work too but it just that this method gets called after first render. But there is no impact in your component

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.