Unwanted React Component rerender?

dorkycam

So this is a form where the user can add sections to add a question (to build a quiz) and I notice that when I fill in the Answer Choices and put a file in my dropZone (drop works but doesn't update correctly, you can ignore this) the Answer Choices and dropZone rerender and the fields like refresh and become empty.

I am not entirely sure why this is happening, i have tried looking at similar issues but I couldn't get it to work. Here is my CodeSandbox with my App.

I am thinking that it may be the addQuestion function in my Questions component. Here's the code for that:

  addQuestion = question => {
    questionIdx++;
    var newQuestion = { uniqueId: uuid(), question: "" }
    this.setState(prevState => ({
      questions: [...prevState.questions, newQuestion]
    }));
    return { questions: newQuestion }
  };

I am new to React and Js so any tips/explanations will do loads of help. Thanks!

spakmad

When you add a new question, and update the Questions components state variable questions (Array type), the whole component (Questions and its children) goes through an update process where it recalculates the output DOM tree (a virtual DOM) based on the new state and compares it to the pre-state-change virtual DOM. Before anything is 'rerendered' it checks to see if the virtual DOMs of the two versions are different among other things, if they are it will rerender the parts that change as efficiently as possible.

In your case, the recalculation sees many Answers component inside of many Question and since Answers doesn't have any props, it is basically a fresh render to its initial state which includes 4 empty inputs. The simplest solution I can think of is in the state of the Questions component, make sure each object in the this.state.questions Array has an answers attribute (of type Array of Objects). In the addQuestion method modify var newQuestion = { uniqueId: uuid(), question: "" }; to includes this data on the answers related to that question.

Then when rendering each individual question, pass this answer data (Array of answers) as a prop to Answers and in turn to each individual Answer component based on the index (an Object or a String). At the same time, you must pass an updateAnswers method as a prop to Question that in turn is passed to Answers and Answer, that is called when an Answers input field is changed. The question id and the id of the answer needs to be passed to this method to ultimately modify the answer data that should be now stored in the state of the Questions component. I adjusted the code from the sandbox below to work along these lines, although I didn't make sure to cleanup all the damage:

import React, { Component } from "react";
import "./App.css";

var uuid = require("uuid-v4");
// Generate a new UUID
var myUUID = uuid();
// Validate a UUID as proper V4 format
uuid.isUUID(myUUID); // true

class DropZone extends Component {
  constructor(props) {
    super(props);
    this.state = {
      file: "",
      fileId: uuid(),
      className: "dropZone"
    };
    this.handleChange = this.handleChange.bind(this);
    this._onDragEnter = this._onDragEnter.bind(this);
    this._onDragLeave = this._onDragLeave.bind(this);
    this._onDragOver = this._onDragOver.bind(this);
    this._onDrop = this._onDrop.bind(this);
  }

  handleChange(file = "") {
    this.setState({
      file: URL.createObjectURL(file)
    });
    //document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
  }

  componentDidMount() {
    window.addEventListener("mouseup", this._onDragLeave);
    window.addEventListener("dragenter", this._onDragEnter);
    window.addEventListener("dragover", this._onDragOver);
    document
      .getElementById("dragbox")
      .addEventListener("dragleave", this._onDragLeave);
    window.addEventListener("drop", this._onDrop);
  }

  componentWillUnmount() {
    window.removeEventListener("mouseup", this._onDragLeave);
    window.removeEventListener("dragenter", this._onDragEnter);
    window.addEventListener("dragover", this._onDragOver);
    document
      .getElementById("dragbox")
      .removeEventListener("dragleave", this._onDragLeave);
    window.removeEventListener("drop", this._onDrop);
  }

  _onDragEnter(e) {
    e.stopPropagation();
    e.preventDefault();
    return false;
  }

  _onDragOver(e) {
    e.preventDefault();
    e.stopPropagation();
    return false;
  }

  _onDragLeave(e) {
    e.stopPropagation();
    e.preventDefault();
    return false;
  }

  _onDrop(e, event) {
    e.preventDefault();
    this.handleChange(e.dataTransfer.files[0]);
    let files = e.dataTransfer.files;
    console.log("Files dropped: ", files);
    // Upload files
    console.log(this.state.file);
    return false;
  }

  render() {
    const uniqueId = this.state.fileId;
    return (
      <div>
        <input
          type="file"
          id={uniqueId}
          name={uniqueId}
          class="inputFile"
          onChange={e => this.handleChange(e.target.files[0])}
        />
        <label htmlFor={uniqueId} value={this.state.file}>
          {this.props.children}
          <div className="dropZone" id="dragbox" onChange={this.handleChange}>
            Drop or Choose File
            <img src={this.state.file} id="pic" name="file" accept="image/*" />
          </div>
        </label>
        <div />
      </div>
    );
  }
}

class Answers extends Component {
  constructor(props) {
    super(props);
    this.state = {
      answers: props.answers,
    };
    this.handleUpdate = this.handleUpdate.bind(this);
  }

  // let event = {
  //   index: 1,
  //   value: 'hello'
  // };
  handleUpdate(event) {
    //if ("1" == 1) // true
    //if ("1" === 1) //false
    // var answers = this.state.answers;
    // answers[event.index] = event.value;
    // this.setState(() => ({
    //   answers: answers
    // }));

    var answers = this.state.answers.slice();

    for (var i = 0; i < answers.length; i++) {
      if (answers[i].answerId == event.answerId) {
        answers[i].answer = event.value;
        break;
      }
    }
    this.setState(() => ({
      answers: answers
    }));
    this.props.updateAnswers(answers)

    console.log(event);
  }

  render() {
    return (
      <div id="answers">
        Answer Choices<br />
        {this.state.answers.map((value, index) => (
          <Answer
            key={`${value}-${index}`}
            onUpdate={this.handleUpdate}
            value={value}
            number={index}
            name="answer"
          />
        ))}
      </div>
    );
  }
}

class Answer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      answer: props.value.answer,
      answerId: props.value.answerId,
      isCorrect: props.value.isCorrect,
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    this.setState({
      answer: value
    });
    this.props.onUpdate({
      answerId: this.state.answerId,
      value
    });

    // let sample = {
    //   kyle: "toast",
    //   cam: "pine"
    // };

    // sample.kyle
    // sample.cam
  }
  render() {
    return (
      <div>
        <input type="checkbox" />
        <input
          type="text"
          value={this.state.answer}
          onChange={this.handleChange}
          key={this.state.answerId}
          name="answer"
        />
        {/*console.log(this.state.answerId)*/}
      </div>
    );
  }
}

var questionIdx = 0;

class Questions extends Component {
  constructor(props) {
    super(props);
    this.state = {
      questions: []
    };
    this.handleUpdate = this.handleUpdate.bind(this);
    this.handleUpdate = this.handleUpdate.bind(this);
    this.removeQuestion = this.removeQuestion.bind(this);
  }

  handleUpdate(event) {
    //if ("1" == 1) // true
    //if ("1" === 1) //false
    var questions = this.state.questions.slice();

    for (var i = 0; i < questions.length; i++) {
      if (questions[i].uniqueId == event.uniqueId) {
        questions[i].question = event.value;
        break;
      }
    }
    this.setState(() => ({
      questions: questions
    }));

    console.log(event, questions);
  }

  updateAnswers(answers, uniqueId) {
    const questions = this.state.questions
    questions.forEach((question) => {
      if (question.uniqueId === uniqueId) {
        question.answers = answers
      }
    })
    this.setState({
      questions,
    })
  }

  addQuestion = question => {
    questionIdx++;
    var newQuestion = { 
      uniqueId: uuid(),
      question: "",
      answers: [
        { answer: "", answerId: uuid(), isCorrect: false,},
        { answer: "", answerId: uuid(), isCorrect: false,},
        { answer: "", answerId: uuid(), isCorrect: false,},
        { answer: "", answerId: uuid(), isCorrect: false,}]
      }
    this.setState(prevState => ({
      questions: [...prevState.questions, newQuestion]
    }));
    return { questions: newQuestion };
  };

  removeQuestion(uniqueId, questions) {
    this.setState(({ questions }) => {
      var questionRemoved = this.state.questions.filter(
        props => props.uniqueId !== uniqueId
      );
      return { questions: questionRemoved };
    });
    console.log(
      "remove button",
      uniqueId,
      JSON.stringify(this.state.questions, null, " ")
    );
  }

  render() {
    return (
      <div id="questions">
        <ol id="quesitonsList">
          {this.state.questions.map((value, index) => (
            <li key={value.uniqueId}>
              {
                <RemoveQuestionButton
                  onClick={this.removeQuestion}
                  value={value.uniqueId}
                />
              }
              {
                <Question
                  onUpdate={this.handleUpdate}
                  value={value}
                  number={index}
                  updateAnswers={(answers) => this.updateAnswers(answers, value.uniqueId) }
                />
              }
              {<br />}
            </li>
          ))}
        </ol>
        <AddQuestionButton onClick={this.addQuestion} />
      </div>
    );
  }
}

class Question extends Component {
  constructor(props) {
    super(props);
    this.state = {
      question: props.value.question,
      uniqueId: props.value.uniqueId,
      answers: props.value.answers,
    };
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    this.setState({
      question: value
    });
    this.props.onUpdate({
      uniqueId: this.state.uniqueId,
      value
    });
  }

  render() {
    return (
      <div id={"questionDiv" + questionIdx} key={myUUID + questionIdx + 1}>
        Question<br />
        <input
          type="text"
          value={this.state.question}
          onChange={this.handleChange}
          key={this.state.uniqueId}
          name="question"
        />
        <DropZone />
        <Answers updateAnswers={this.props.updateAnswers} answers={this.state.answers} />
      </div>
    );
  }
}

class IntroFields extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      author: ""
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    const name = target.name;
    console.log([name]);
    this.setState((previousState, props) => ({
      [name]: value
    }));
  }

  render() {
    return (
      <div id="IntroFields">
        Title:{" "}
        <input
          type="text"
          value={this.state.title}
          onChange={this.handleChange}
          name="title"
        />
        Author:{" "}
        <input
          type="text"
          value={this.state.author}
          onChange={this.handleChange}
          name="author"
        />
      </div>
    );
  }
}

class AddQuestionButton extends Component {
  addQuestion = () => {
    this.props.onClick();
  };

  render() {
    return (
      <div id="addQuestionButtonDiv">
        <button id="button" onClick={this.addQuestion} />
        <label id="addQuestionButton" onClick={this.addQuestion}>
          Add Question
        </label>
      </div>
    );
  }
}

class RemoveQuestionButton extends Component {
  removeQuestion = () => {
    this.props.onClick(this.props.value);
  };

  render() {
    return (
      <div id="removeQuestionButtonDiv">
        <button id="button" onClick={this.removeQuestion} key={uuid()} />
        <label
          id="removeQuestionButton"
          onClick={this.removeQuestion}
          key={uuid()}
        >
          Remove Question
        </label>
      </div>
    );
  }
}

class BuilderForm extends Component {
  render() {
    return (
      <div id="formDiv">
        <IntroFields />
        <Questions />
      </div>
    );
  }
}
export default BuilderForm;

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Best pattern to rerender my react component?

From Dev

Forcing a react functional component to trigger a rerender

From Dev

React rerender component while state not changed?

From Dev

React Native: Component rerender but props has not changed

From Java

Can you force a React component to rerender without calling setState?

From Dev

Rerender React component with new AJAX query parameter values

From Dev

React-redux component does not rerender on store state change

From Dev

How can you trigger a rerender of a React js component every minute?

From Dev

Unwanted state changes in a class component with React

From Dev

React to rerender immediately

From Dev

React ReRender in Redux

From Dev

React ReRender in Redux

From Dev

React to rerender immediately

From Dev

Ember reload, rerender component template

From Dev

React Native: Does updating a ListView dataSource through setState rerender the whole component?

From Dev

React: Within a container how to render a component on to another one without rerender others?

From Java

Rerender view on browser resize with React

From Dev

React rerender only one child

From Dev

Telling a component to rerender itself via the parent

From Dev

Rerender the entire component if a specific prop changes

From Dev

React Rerender in case of cache disable in Chrome

From Dev

React onChange rerender dynamic input fields strange

From Dev

React js rerender of large number of components in a form

From Dev

React doesn't rerender on props change

From Dev

React Native: TabBarIOS, rerender tabs on click

From Dev

Angular2 : Rerender component when there is a change for inner property of an object

From Dev

Using a4j , how would you rerender a dojo component?

From Dev

Vue child component didn't rerender when props changed?

From Dev

React useState rerender trigger behaves different depending of when its called

Related Related

  1. 1

    Best pattern to rerender my react component?

  2. 2

    Forcing a react functional component to trigger a rerender

  3. 3

    React rerender component while state not changed?

  4. 4

    React Native: Component rerender but props has not changed

  5. 5

    Can you force a React component to rerender without calling setState?

  6. 6

    Rerender React component with new AJAX query parameter values

  7. 7

    React-redux component does not rerender on store state change

  8. 8

    How can you trigger a rerender of a React js component every minute?

  9. 9

    Unwanted state changes in a class component with React

  10. 10

    React to rerender immediately

  11. 11

    React ReRender in Redux

  12. 12

    React ReRender in Redux

  13. 13

    React to rerender immediately

  14. 14

    Ember reload, rerender component template

  15. 15

    React Native: Does updating a ListView dataSource through setState rerender the whole component?

  16. 16

    React: Within a container how to render a component on to another one without rerender others?

  17. 17

    Rerender view on browser resize with React

  18. 18

    React rerender only one child

  19. 19

    Telling a component to rerender itself via the parent

  20. 20

    Rerender the entire component if a specific prop changes

  21. 21

    React Rerender in case of cache disable in Chrome

  22. 22

    React onChange rerender dynamic input fields strange

  23. 23

    React js rerender of large number of components in a form

  24. 24

    React doesn't rerender on props change

  25. 25

    React Native: TabBarIOS, rerender tabs on click

  26. 26

    Angular2 : Rerender component when there is a change for inner property of an object

  27. 27

    Using a4j , how would you rerender a dojo component?

  28. 28

    Vue child component didn't rerender when props changed?

  29. 29

    React useState rerender trigger behaves different depending of when its called

HotTag

Archive