React - SetState before rendering - javascript

Table on Render
Table on Render after new row
Table on Render after second new row
I have a page rendering servers names title etc..
It has a count of the servers that are online,offline and warning.
When it first renders it works fine, but when i add a server and it to the array.
It updates the rows but not the server count because it hasnt rendered until after i update the server count.
I used componentDidMount to fix that on startup but not on update.
Not sure if you need more information.
class App extends Component {
constructor(props) {
super(props);
this.state = {
serverCount: [],
servers: [
{
host: "192.168.57.2",
status: "Online",
title: "Server",
location: "Location"
},
{
host: "192.168.57.1",
status: "Offline",
title: "Server",
location: "Location"
},
{
host: "192.168.57.0",
status: "Warning",
title: "Server",
location: "Location"
}
]
};
this.handleFormData = this.handleFormData.bind(this);
}
handleServerCount() {
let newArr = [0,0,0,0]
this.state.servers.map(data => {
let status = data.status
if(status === "Online"){
newArr[1]++
} else if (status === "Warning") {
newArr[2]++
} else {
newArr[3]++
}
newArr[0]++
})
return newArr;
}
handleFormData(data) {
let newArr = this.handleServerCount();
let newState = this.state.servers.slice();
newState.push(data);
this.setState({
servers: newState,
serverCount: newArr
});
}
componentDidMount(){
let newArr = this.handleServerCount();
this.setState({
serverCount: newArr
})
}
render() {
return (
<Default>
<div className="upperContainer">
<ServerCount serverCount={this.state.serverCount} />
<RequestTimer />
</div>
<ServerList serverList={this.state.servers} />
<Input handleFormData={this.handleFormData} />
</Default>
);
}
}
class ServerList extends Component {
render() {
const rows = [];
this.props.serverList.forEach((server) => {
rows.push(
<ServerRow key={server.host}
title={server.title}
host={server.host}
location={server.location}
status={server.status}/>
)
})
return (
<ListTable>
<ServerHeader/>
{rows}
</ListTable>
)
}
}
const ServerCount = (props) => {
return (
<CountContainer>
<div className="circleContainer">
<div className="total serverCircle">
{props.serverCount[0]}
</div>
Total
</div>
<div className="circleContainer">
<div className="Online serverCircle">
{props.serverCount[1]}
</div>
Online
</div>
<div className="circleContainer">
<div className="Warning serverCircle">
{props.serverCount[2]}
</div>
Warning
</div>
<div className="circleContainer">
<div className="Offline serverCircle">
{props.serverCount[3]}
</div>
Offline
</div>
</CountContainer>
)
}
class Input extends Component {
constructor(props) {
super(props);
this.state = {
host: "",
title: "",
location: ""
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e){
const {name, value} = e.target;
this.setState({
[name]: value
})
}
handleSubmit(e) {
e.preventDefault();
var newServer = {
host: this.state.host,
status: "Warning",
title: this.state.title,
location: this.state.location,
}
this.setState({
host:'',
title:'',
location:''
})
this.props.handleFormData(newServer);
}
render() {
return (
<InputForm onSubmit={this.handleSubmit}>
<input name="host" value={this.state.host} onChange={this.handleChange} placeholder="10.10.10.0"></input>
<div><span>Unknown</span></div>
<input name="title" value={this.state.title} onChange={this.handleChange} placeholder="Live Server"></input>
<input name="location" value={this.state.location} onChange={this.handleChange} placeholder="Knutsford"></input>
<button type="submit"></button>
</InputForm>
);
}
}

handleServerCount() {
let newArr = [...this.state.serverCount]
this.state.servers.map(data => {
let status = data.status
if(status === "Online"){
newArr[1]++
} else if (status === "Warning") {
newArr[2]++
} else {
newArr[3]++
}
newArr[0]++
})
return newArr;
}

Related

My forEach statement is repeating more than it should

I am using firebase and react js. my forEach loop is only supposed to repeat for the number of objects I have stored in the firebase database but instead of repeated the expected 3 times it will repeat 8 or 9 or something like that. Also when I run the code a second time it will repeat super fast and fill up my console.
constructor() {
super()
this.state = {
warmupList: [],
title: "",
id: ""
}
}
handleInputChange = (e) => {
this.setState({
title: e.target.value
})
}
SetActiveWarmup(id) {
const warmupRef = firebase.database().ref("Warmups")
warmupRef.on("value",(snapshot) => {
const warmups = snapshot.val()
this is where the for each is
Object.keys(warmups).forEach(key => {
if(id === key) {
warmupRef.child(key).update({
activity: "True"
})
} else {
warmupRef.child(key).update({
activity: "False"
})
}
console.log("has run")
})
})
}
createWarmup = (e) => {
e.preventDefault()
if(this.state.title === "") return
const warmupRef = firebase.database().ref("Warmups")
var key = warmupRef.push().key
warmupRef.child(key).set({
title: this.state.title,
activity: "True",
id: key
})
this.setState({
title: ""
})
}
componentWillMount() {
this._isMounted = true;
const warmupRef = firebase.database().ref("Warmups")
warmupRef.on("value",(snapshot) => {
const warmups = snapshot.val()
const warmupList = []
for(let id in warmups) {
warmupList.push(warmups[id])
}
this.setState({
warmupList: warmupList
})
})
}
render() {
return (
<div className={`warmup${this.props.activity}`}>
<div className="newWarmup__bottom">
<form onSubmit={this.createWarmup}>
<input placeholder="My Warmup..."
value={this.state.title}
onChange={this.handleInputChange} />
<CircleButton click={this.createWarmup}
Icon={AddIcon} color="Green" />
</form>
</div>
<div className="warmup__select">
{this.state.warmupList.map(warmup =>
<SelectWarmup key={v4()}
text={warmup.title}
activity={warmup.activity}
click={() => this.SetActiveWarmup(warmup.id)} />
)}
</div>
</div>
)
}
}

React - Render Component between two existing ones

I am pretty new on using React, what i'm trying to build is a dynamic form in which user can add/remove fields, the problems come when rendering after a row (field) is added
Here is my Row Component, which I use as a template to fill by props
class Row extends React.Component {
constructor(props){
super(props);
this.addLine = this.addLine.bind(this)
this.handleTitleChange = this.handleTitleChange.bind(this)
this.handleInquisitionChange = this.handleInquisitionChange.bind(this)
}
state = {
pos: this.props.pos,
title: this.props.title,
inquisition: this.props.inquisition
}
addLine() {
this.props.addLine(this.state.pos);
}
handleTitleChange = async (event) => {
await this.setState({title: event.target.value});
this.props.updateRowState(this.state.pos, this.state.title, "title")
}
handleInquisitionChange = async (event) => {
await this.setState({inquisition: event.target.value});
this.props.updateRowState(this.state.pos, this.state.inquisition, "inquisition")
}
render(){
return(
<div className="w3-row odg-line">
<div className="w3-col m2" style={{paddingRight: "8px"}}>
<input type="text" name="titolo[]" placeholder="Argomento" style={{width:"100%"}} onChange={this.handleTitleChange} required/>
</div>
<div className="w3-col m4" style={{paddingRight: "8px"}}>
<textarea form="convocazione" name="istruttoria[]" placeholder="Istruttoria" style={{width:"100%"}} onChange={this.handleInquisitionChange} required></textarea>
</div>
<div className="w3-col m1">
<button type="button" style={{padding:0, height: "24px", width: "24px"}} className="w3-red w3-button w3-hover-white" onClick={() => this.addLine()}>+</button>
</div>
</div>
)
}
}
And this is its parent Convoca, as you can see by its addLine method whenever a "plus" button is pressed it pushes a row after that and updates component state, as far as I know this should cause the component to render again but when it comes it just adds the new one after the already rendered ones
class Convoca extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this);
this.addLine = this.addLine.bind(this);
}
state = {
rows: [
{pos:0, title: "", inquisition: ""},
{pos:1, title: "", inquisition: ""}
]
}
async addLine(position){
let buffer = this.state.rows;
buffer.splice(position+1, 0, {pos: position+1, title: "", inquisition: ""})
for(let i = position+2; i<buffer.length; i++){
buffer[i].pos++;
}
await this.setState({rows: buffer})
}
handleChangeState = async (pos, val, field) => {
let buffer = this.state.rows;
if(field === "title") buffer[pos].title = (field === "title" ? val : null);
if(field === "inquisition") buffer[pos].inquisition = (field === "inquisition" ? val : null);
await this.setState({rows: buffer})
}
handleSubmit(){
console.log("submitted")
}
render() {
return(
<div className="w3-main" style={{marginLeft:"340px", marginRight:"40px"}}>
<form action="/convocazione" id="convocazione">
{ this.state.rows.map((row) => (
<Row updateRowState={(pos, val, field) => this.handleChangeState(pos, val, field)} addLine={(pos)=>this.addLine(pos)} pos={row.pos} title={row.title} inquisition={row.inquisition}></Row>)
) }
<input className="w3-red w3-button w3-hover-white" type="submit" value="Convoca"/>
</form>
</div>
);
}
}
I would implement addLine function another way
Take a look at the snippet.
const createElement = React.createElement;
class Row extends React.Component {
render() {
const {
position
} = this.props;
return createElement('div', null, [
createElement('input', {
type: "text",
value: this.props.title
}),
createElement('button', {
type: "button",
onClick: () => this.props.onAdd(position)
}, '+')
]);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [{
id: 0,
position: 0,
title: 'id 0 position 0'
},
{
id: 1,
position: 1,
title: 'id 1 position 1'
},
]
};
this.addLine = this.addLine.bind(this);
}
addLine(position) {
const {
rows
} = this.state
const newId = rows.reduce((acc, row) => acc > row.id ? acc : row.id, 0) + 1
position = position + 1;
const newRows = [
...rows.filter(row => row.position < position),
{
id: newId,
position,
title: `id ${newId} position ${position}`
},
...rows.filter(row => row.position >= position).map(row => ({ ...row,
position: row.position + 1,
title: `id ${row.id} position ${row.position + 1}`
}))
]
newRows.sort((prev, next) => prev.position - next.position)
this.setState({
rows: newRows
})
}
render() {
const items = this.state.rows.map(item =>
createElement(Row, {
key: item.id,
title: item.title,
position: item.position,
onAdd: this.addLine
})
)
return createElement('form', null, items);
}
}
var rootElement = createElement(App, {}, )
ReactDOM.render(rootElement, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root">
</div>
Make sure every Row has key prop with unique id

Delete an item without using index React

I want to delete the item which is right clicked. I am using onContextMenu and event.preventDefault to avoid showing the context menu. However, I don't know how to delete an item without the index. The requirement of this is not using index to do deletion.
The class App:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: activities,
filteredActivities: activities,
isShow: false,
};
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleClickChange = this.handleClickChange.bind(this);
this.addItem = this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
deleteItem(item) {
item.preventDefault();
const activities = this.state.activities.slice();
var response = confirm("Sure delete?");
if (response == true) {
// how to delete please?
this.setState({
activities: activities
});
}
}
render() {
const filteredActivities = this.props.filteredActivities;
const isShow = this.state.isShow;
return(
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} onClickSearch={this.handleClickChange} onClickAdd={this.addItem} />
{ isShow ? <SearchBar inputChanged={this.handleSearchChange} /> : null }
<Content activities={this.state.filteredActivities} onRightClick={this.deleteItem}/>
</div>
</div>
);
}
}
The structure of data:
const activities = [
{img_url: "assets/dog.jpg", time: "A hour ago", content: "Have lunch.", comment_count: "2" },
{img_url: "assets/dog.jpg", time: "5 hour ago", content: "Have breakfast.", comment_count: "0" },
{img_url: "assets/dog.jpg", time: "6 hour ago", content: "Get up.", comment_count: "1" }
];
The class content:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{this.props.activities.map(activity =>
<ActivityItem img_url={activity.img_url} time={activity.time}
content={activity.content} comment_count={activity.comment_count} onContextMenu={this.props.onRightClick}/>
)}
</div>
);
}
}
The class item to show:
class ActivityItem extends React.Component{
render() {
return (
<div className="item" {...this.props}>
<div className="avatar">
<img src={this.props.img_url} />
</div>
<span className="time">
{this.props.time}
</span>
<p>
{this.props.content}
</p>
<div className="commentCount">
{this.props.comment_count}
</div>
</div>
);
}
}
You can do this way
deleteItem = (item) => {
const activities = this.state.activities.slice(item, 1);
var response = confirm("Sure delete?");
if (response == true) {
// how to delete please?
this.setState({
activities: activities
});
}
}
The class content:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{this.props.activities.map(activity =>
<ActivityItem img_url={activity.img_url} time={activity.time}
content={activity.content} comment_count={activity.comment_count}
onContextMenu={() => this.props.onRightClick(activity)} />
)}
</div>
);
}
}
deleteItem need to modify a lot. The const activities = this.state.activities.slice(); is wrong and need to modify. I also need to find the item index. Then I just splice the item out.
deleteItem = item => {
event.preventDefault();
let activities = this.state.activities;
var response = confirm("Sure delete?");
if (response == true) {
for(var i = 0; i < activities.length; i++){
if(activities[i] == item){
activities.splice(i,1);
};
};
this.setState({
activities: activities
});
};
}
The class content:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{this.props.activities.map(activity =>
<ActivityItem img_url={activity.img_url} time={activity.time}
content={activity.content} comment_count={activity.comment_count}
onContextMenu={() => this.props.onRightClick(activity)} />
)}
</div>
);
}
}
You can filter your activities array.
deleteItem(item) {
item.preventDefault();
const activities = this.state.activities;
var response = confirm("Sure delete?");
if (response == true) {
activities.filter((i) => {
if (i === item) {
items.splice(items.indexOf(i), 1);
}
});
this.setState({
activities: activities
});
}
}

How do I get the ref value from a drop down box passed to its sibling component?

I am building a chat app with socket.io and I have a dashboard component with 2 child components called Room.js and Chat.js. My Room component is responsible for selecting which chat room you want to join. In the select tag I am giving it a ref to know the value. How would I access that value in my Chat component so I could emit messages only in that room?
I posted a working version below the first snippet with messaging and everything all in one component, but I need them to be separate components.
I am still a react beginner.
class Room extends Component {
constructor(props) {
super(props);
this.state = {
};
this.joinRoom = this.joinRoom.bind(this);
this.joinSuccess = this.joinSuccess.bind(this);
}
componentDidMount() {
this.socket = io("http://localhost:4444")
this.socket.on('message dispatched', this.updateMessages)
this.socket.on('welcome', this.setUserId)
this.socket.on('room joined', this.joinSuccess)
this.joinRoom()
}
joinRoom() {
this.socket.emit('join room', {
room: this.refs.room.value
})
}
joinSuccess(room) {
console.log("you successfully joined room " + room)
}
render() {
return (
<Background>
<Container className="animated fadeInDownBig">
{" "}
<Logo> Sketchful </Logo>
<Info>Join a Room</Info>
<select ref='room' defaultValue='Global' onChange={this.joinRoom}>
<option>Global</option>
<option>Stark</option>
<option>Lannister</option>
<option>Targaryen</option>
<option>Tyrell</option>
<option>Baratheon</option>
<option>Greyjoy</option>
</select>
<Link to="/dashboard">
<Button onClick={this.props.handleEnter}> Enter </Button>
</Link>{" "}
</Container>
</Background>
);
}
}
export default Room;
If I do this it works, but I want the Chat component to be responsible for emitting the messages.
class Room extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
};
this.updateMessages = this.updateMessages.bind(this);
this.sendMessage = this.sendMessage.bind(this);
this.joinRoom = this.joinRoom.bind(this);
this.joinSuccess = this.joinSuccess.bind(this);
}
componentDidMount() {
this.socket = io("http://localhost:4444")
this.socket.on('message dispatched', this.updateMessages)
this.socket.on('welcome', this.setUserId)
this.socket.on('room joined', this.joinSuccess)
this.joinRoom()
}
updateMessages(message) {
const updatedMessages = this.state.messages.slice()
updatedMessages.push(message)
this.setState({
messages: updatedMessages
})
}
sendMessage() {
this.socket.emit('message sent', {
message: this.refs.message.value,
room: this.refs.room.value
})
this.refs.message.value = '';
}
joinRoom() {
this.socket.emit('join room', {
room: this.refs.room.value
})
}
joinSuccess(room) {
console.log("you successfully joined room " + room)
}
render() {
const messages = this.state.messages.map((e,i) => {
const styles = e.user === this.state.userID ? {alignSelf: "flex-end", backgroundColor: "#2d96fb", color: "white"} : {alignSelf: "flex-start", backgroundColor: "#e5e6ea"}
return (
<p key={i} style={styles}>{e.message}</p>
)
})
console.log(this.props.room)
return (
<Background>
<Container className="animated fadeInDownBig">
{" "}
<Logo> Sketchful </Logo>
<Info>Join a Room</Info>
<select ref={this.props.room} defaultValue='Global' onChange={this.joinRoom}>
<option>Global</option>
<option>Stark</option>
<option>Lannister</option>
<option>Targaryen</option>
<option>Tyrell</option>
<option>Baratheon</option>
<option>Greyjoy</option>
</select>
<div className="messages">
{messages}
</div>
<div className="input">
<input ref="message" />
<button onClick={this.sendMessage}>Send</button>
</div>
<Link to="/dashboard">
<Button onClick={this.props.handleEnter}> Enter </Button>
</Link>{" "}
</Container>
</Background>
);
}
}
export default Room;
class Chat extends Component {
constructor(props) {
super(props);
this.state = {
// words: ["cat", "dog", "sun", "cup", "pie", "bug", "snake", "tree"],
messages: [],
message: "",
correct: '',
typing: false,
timeout: undefined,
};
// this.socket = io.connect("http://localhost:4444");
this.handleEnter = this.handleEnter.bind(this)
}
componentDidMount() {
this.socket = io.connect("http://localhost:4444");
this.socket.on("chat", msg => {
let messages = this.state.messages
messages.push(msg)
this.setState({
messages: messages
})
});
}
componentWillUnmount() {
this.socket.close();
}
updateMessage = e => {
this.setState({
message: e.target.value
});
};
async handleEnter (e) {
if(this.state.message) {
if (e.key === "Enter" ) {
this.socket.emit("chat", {
name: this.props.user.username,
message: this.state.message,
timestamp: new Date().toISOString(),
});
this.setState({
message: ""
});
const response = await axios.post(`/api/create`, {message: this.state.message})
this.props.getUserData(response)
let words = this.props.words;
for (let i = 0; i < words.length; i++) {
if (this.state.message === this.props.word) {
this.setState({
correct: 'Correct!'
})
}
}
}
}
};
handleClick = () => {
console.log('clicked')
}
render() {
console.log(this.state.message)
return (
<div className="chat">
<Messages messages={this.state.messages} user={this.props.user.username}/>
<p>{this.state.correct}</p>
<textarea
value={this.state.message}
onKeyPress={this.handleEnter}
onChange={this.updateMessage}
className="message"
placeholder="Type a message... "
type="text"
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user,
id: state.id
};
}
export default connect(
mapStateToProps,
{ getUserData }
)(Chat);
Server
io.on("connection", socket => {
console.log("a user connected");
socket.on("disconnect", function() {
console.log("user disconnected");
});
socket.on('join room', data => {
console.log('Room joined', data.room)
socket.join(data.room);
io.to(data.room).emit('room joined', data.room);
})
socket.on("chat", data => {
console.log(data.room)
io.in(data.room).emit("chat", data);
});
This is a good use case for state not refs, personally I avoid using refs, state is more responsive.
Upon clicking the room, you can setState with the room is or singing and then you can pass this id as a prop to the chat component.

Can't seem to keep my DropZone state(s) updated/always rerenders React?

So my app is a form that has dropZones (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!
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.

Categories

Resources