Reputation: 529
So my app is a form that has dropZone
s (amongst other things) and an Add Questions
button that adds another dropZone
to the form. Whenever I put an image in my dropZone
and then click Add Question
the image disappears. Here's a CodeSandbox of the whole app.
But if you prefer relevant code only, here's my DropZone
component followed by my AddQuestionButton
component:
class DropZone extends Component {
constructor(props) {
super(props);
this.dropZoneRef = React.createRef();
this.state = {
fileBlob: props.fileBlob,
fileId: props.fileId
};
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({
fileBlob: URL.createObjectURL(file)
});
console.log(this.state.fileBlob + "OMG")
//document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
}
handleUpdate(){
}
componentDidMount(event) {
this.dropZoneRef.current.addEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.addEventListener("dragenter", this._onDragEnter);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.addEventListener("dragleave", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("drop", this._onDrop);
window.addEventListener("dragover",function(e){
e = e || event;
e.preventDefault();
},false);
window.addEventListener("drop",function(e){
e = e || event;
e.preventDefault();
},false);
}
componentWillUnmount() {
this.dropZoneRef.current.removeEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("dragenter", this._onDragEnter);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.removeEventListener("dragleave", this._onDragLeave);
this.dropZoneRef.current.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.fileBlob);
return false;
}
render() {
const labelId = uuid();
return (
<div>
<input
type="file"
id={labelId}
name={this.state.fileBlobId}
className="inputFile"
onChange={e => this.handleChange(e.target.files[0])}
/>
<label htmlFor={labelId} value={this.state.fileBlob}>
{this.props.children}
<div className="dropZone" id="dragbox" key={this.state.fileBlobId} ref={this.dropZoneRef} onChange={this.handleChange} onDrop={this._onDrop}>
Drop or Choose File {console.log(this.dropZoneRef)}
<img src={this.state.fileBlob} id="pic" name="file" accept="image/*" />
</div>
</label>
<div />
</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>
);
}
}
And here's the direct parent of the DropZone
component, Question
:
class Question extends Component {
constructor(props) {
super(props);
this.state = {
question: props.value.question,
uniqueId: props.value.uniqueId,
answers: props.value.answers,
file: props.file
};
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
});
}
handleUpdate(event, file) {
//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].file = event.value;
break;
}
}
this.setState(() => ({
questions: questions
}));
console.log(event, questions);
}
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 file={this.state.file}/>
<Answers
updateAnswers={this.props.updateAnswers}
answers={this.state.answers}
/>
</div>
);
}
}
And Question
's parent component, `Questions':
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
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: "",
file: { fileBlob: "", fileId: uuid()},
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>
);
}
}
Thanks!
Upvotes: 0
Views: 965
Reputation: 910
Only keep state in the top level component, and you should be good to go.
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.dropZoneRef = React.createRef();
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.props.updateFile(URL.createObjectURL(file), this.props.file.fileId);
//document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
}
componentDidMount(event) {
this.dropZoneRef.current.addEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.addEventListener("dragenter", this._onDragEnter);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.addEventListener("dragleave", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("drop", this._onDrop);
window.addEventListener(
"dragover",
function(e) {
e = e || event;
e.preventDefault();
},
false
);
window.addEventListener(
"drop",
function(e) {
e = e || event;
e.preventDefault();
},
false
);
}
componentWillUnmount() {
this.dropZoneRef.current.removeEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.removeEventListener(
"dragenter",
this._onDragEnter
);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.removeEventListener(
"dragleave",
this._onDragLeave
);
this.dropZoneRef.current.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
return false;
}
render() {
const labelId = uuid();
return (
<div>
<input
type="file"
id={labelId}
name={this.props.file.fileId}
className="inputFile"
onChange={e => this.handleChange(e.target.files[0])}
/>
<label htmlFor={labelId} value={this.props.file.fileBlob}>
{this.props.children}
<div
className="dropZone"
id="dragbox"
key={this.props.file.fileId}
ref={this.dropZoneRef}
onChange={this.handleChange}
onDrop={this._onDrop}
>
Drop or Choose File {console.log(this.dropZoneRef)}
<img
src={this.props.file.fileBlob}
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) {
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.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
});
}
updateFile(fileBlob, fileId) {
const questions = this.state.questions;
questions.forEach(question => {
if (question.file.fileId === fileId) {
question.file.fileBlob = fileBlob;
}
});
this.setState({
questions
});
}
addQuestion = question => {
questionIdx++;
var newQuestion = {
uniqueId: uuid(),
question: "",
file: { fileBlob: {}, fileId: uuid() },
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)
}
updateFile={this.updateFile.bind(this)}
/>
}
{<br />}
</li>
))}
</ol>
<AddQuestionButton onClick={this.addQuestion} />
</div>
);
}
}
class Question extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.props.onUpdate({
uniqueId: this.props.value.uniqueId,
value
});
}
render() {
return (
<div id={"questionDiv" + questionIdx} key={myUUID + questionIdx + 1}>
Question<br />
<input
type="text"
value={this.props.value.question}
onChange={this.handleChange}
key={this.props.value.uniqueId}
name="question"
/>
<DropZone
file={this.props.value.file}
updateFile={this.props.updateFile}
/>
<Answers
updateAnswers={this.props.updateAnswers}
answers={this.props.value.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;
You shouldn't be passing props into state like here from the Question
component:
this.state = {
question: props.value.question,
uniqueId: props.value.uniqueId,
answers: props.value.answers,
file: props.file
};
Because then you're allowing two different components to have two logical sources of state that they believe to be the same. Keep one source of truth that has everything that its children depend on. If all of the children components don't depend on the data (state), consider moving it down a level to the state of the child, otherwise pass as props.
Upvotes: 1