[英]Unwanted React Component rerender?
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. 因此,这是一种表单,用户可以在其中添加部分以添加问题(以构建测验),并且我注意到当我填写“
Answer Choices
并将文件放置到我的dropZone
(drop可以正常运行,但无法正确更新)可以忽略这一点),然后dropZone
Answer Choices
和dropZone
,字段(如refresh)变为空。
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.
这是我的CodeSandbox和我的应用程序。
I am thinking that it may be the addQuestion
function in my Questions
component. 我认为这可能是我的
Questions
组件中的addQuestion
函数。 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. 我是React和Js的新手,所以任何技巧/解释都可以帮助您。 Thanks!
谢谢!
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. 当您添加新问题并更新
Questions
组件状态变量questions
(数组类型)时,整个组件( Questions
及其子元素)将经历一个更新过程,在此过程中,新组件将基于新问题重新计算输出DOM树(虚拟DOM)。状态并将其与状态更改前的虚拟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. 在“渲染”任何东西之前,它会检查两个版本的虚拟DOM是否彼此不同,如果是,它将尽可能有效地重新渲染那些变化的部分。
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. 在您的情况下,重新计算会在许多
Question
看到许多Answers
组件,并且由于Answers
没有任何道具,因此它基本上是到其初始状态的全新渲染,其中包括4个空输入。 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). 我能想到的最简单的解决方案是在
Questions
组件的状态下,确保this.state.questions
数组中的每个对象都有一个answers
属性(对象数组类型)。 In the addQuestion
method modify var newQuestion = { uniqueId: uuid(), question: "" };
在
addQuestion
方法中,修改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). 然后渲染每个单独的问题时,通过此答案数据(应答的阵列)为道具来
Answers
并依次对每个单独的Answer
基于索引(一个对象或一个字符串)组件。 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 Answer
s input field is changed. 与此同时,你必须通过
updateAnswers
方法作为道具Question
,反过来传递给Answers
和Answer
,当被称为Answer
的输入字段改变。 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. 需要将问题ID和答案ID传递给此方法,以最终修改现在应存储在
Questions
组件状态下的答案数据。 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;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.