Reputation: 91
Recently, I have been working with a dynamic control of input text boxes, each with their own id's. However, I noticed that if I stored an array of elements and each element was an object with different variables and such, any change in on of those variables in the object would not alert React to update the children components(which receive the state through props). I am trying to control the input according to the documentation, but am confused as to why changes in the parent's state of a normal non array and non object variable will be recognized by the children as a change in props and yet not for normal state arrays. Here is my parent code:
import React, {Component} from 'react';
import Music from './music'
import axios from 'axios';
import update from 'immutability-helper';
class Selection {
constructor(){
this.music = '';
this.beginning = '';
this.the_end = '';
}
setTitle=(title)=>{
this.music = title;
}
setStart=(start)=>{
this.beginning = start;
}
setEnd=(end)=>{
this.the_end = end;
}
}
class Practice extends React.Component{
constructor(props){
super(props);
this.state = {
selections: Array(0),
test: 0,
}
this.removeSong = this.removeSong.bind(this);
}
removeSong(index){
var newArray = this.state.selections.slice();
newArray.splice(index, 1);
this.setState({selections: newArray, test: this.state.test+=1});
}
addAnotherSong=()=>{
var newArray = this.state.selections.slice();
newArray.push(new Selection());
this.setState({ selections: newArray, test: this.state.test+=1});
}
render(){
return(
<div>
<button onClick={() => this.props.practice()}>Log Practice Session</button>
<h1>{this.props.time}</h1>
<form >
Description: <input type="form" placeholder="How did it go?" name="fname"/><br/>
</form>
<button onClick={()=>this.addAnotherSong()}>Add Another Piece</button>
<button onClick={()=>this.setState({test: this.state.test})}>Will it now Update?</button>
{
this.state.selections.map((child, index) => (
this.state.selections[index].music,
<Music key={index} number={index} subtract={this.removeSong}
Title={this.state.test} Start={this.state.selections[index].beginning}
End={this.state.selections[index].the_end} changeTitle={this.state.selections[index].setTitle}
changeStart={this.state.selections[index].setStart} changeEnd={this.state.selections[index].setEnd}
/>
))
}
</div>
);
}
}
export default Practice;
As you can see, I have an array in the state with objects constructed from the Selection class. Here are the children that won't update unless the change happens in the non array type "this.state.test" prop.
import React, {Component} from 'react';
import InputBox from './input';
class Music extends React.Component{
constructor(props){
super(props);
}
shouldComponentUpdate(newProps){
if(this.props !== newProps){
return true;
} else{
console.log("wassup");
return false;
}
}
render(){
return(
<div>{this.props.number}
<InputBox cValue={this.props.Title} identity={this.props.number} updateInput={this.props.changeTitle} />
<InputBox cValue={this.props.Start} identity={this.props.number} updateInput={this.props.changeStart} />
<InputBox cValue={this.props.End} identity={this.props.number} updateInput={this.props.changeEnd} />
<button onClick={()=> this.props.subtract(this.props.number)}>DELETE{this.props.number}</button>
{this.props.Title}
</div>
)
}
}
export default Music;
Lastly, here is the children of that child.
import React,{Component} from 'react';
class InputBox extends React.Component{
constructor(props){
super(props);
this.state = { value: '' }
this.handleChange = this.handleChange.bind(this);
}
handleChange(event){
this.setState({value: event.target.value});
this.props.updateInput(this.state.value, this.props.identity);
console.log("test" + this.props.cValue);
}
shouldComponentUpdate(newProps){
if(this.props !== newProps){
console.log("Updating Input Component");
return true;
} else {
console.log("yo");
return false;
}
}
render(){
return(
<input type="text"onChange={this.handleChange} value={this.props.cValue}/>
)
}
}
export default InputBox;
If this is a bad question, please let me know. But, any help is always appreciated. Thankyou
Upvotes: 1
Views: 1119
Reputation: 796
You're treating your state as a mutable object - states in React should never be mutated, and a state change should always be a brand new object.
Take, for example, we have just one Selection
in state.selections
:
[ { music: 'music', beginning: 'beginning', the_end: 'ending' } ]
If the Music
component ends up calling setStart('newBeginning')
, then we end up with this in our state:
[ { music: 'music', beginning: 'newBeginning', the_end: 'ending' } ]
This looks like everything went well, but React won't actually pick up this change by default because state.selections
is still referring to the same array.
To fix it, you're going to have to figure out how to update your array reference while you modify individual items. You've already done this in removeSong
and addAnotherSong
by calling slice
on your current state. Slice
returns a new array, so no issues there.
I'd recommend having methods to modify individual Selection
s in Practice
, as opposed to each Selection
modifying itself.
Something like this could work:
class Practice extends React.Component{
updateSelectionBeginning(index, newBeginning){
var newArray = this.state.selections.slice();
newArray[index].beginning = newBeginning;
this.setState({selections: newArray});
}
}
We'd then pass it down to our children via props, and they'd call updateSelectionBeginning(FOO, BAR)
. That lets us update the state, while also keeping it immutable.
Upvotes: 1