Reputation: 447
I'm new to React and am running into the same problem a few times. In this particular situation, I'm trying to get an option in a select dropdown to update when I update a text input.
I have a parent, App, with the state attribute "directions", which is an array. This gets passed as a property to a child, GridSelector, which creates the text field and dropdown. When the text field is changed, a function triggers to update the parent state. This in turn causes the GridSelector property to update. However, the dropdown values, which are originally generated from that GridSelector property, do not re-render to reflect the new property value.
I'm trying to figure out the most React-ful way to do this and similar manuevers. In the past, I've set a state in the child component, but I think I've also read that is not proper.
My working site is at amaxalaus.bigriverwebdesign.com
Here's the pertinent code from each file:
App.js
class App extends React.Component {
constructor(props){
super(props);
this.state = {
directions: [],
dataRouteDirections: '/wp-json/wp/v2/directions',
currentDirectionsIndex: 0
}
this.addImageToGrid = this.addImageToGrid.bind(this);
this.changeTitle=this.changeTitle.bind(this);
}
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => this.setState({directions:data}));
}
addImageToGrid(image) {
this.refs.grid.onAddItem(image); //passes image add trigger from parent to child
}
createNewDirections(){
var directions= this.state.directions;
var index = directions.length;
var lastDirections = directions[directions.length-1];
var emptyDirections= {"id":0,"acf":{}};
emptyDirections.acf.grid="[]";
emptyDirections.acf.layout="[]";
emptyDirections.title={};
emptyDirections.title.rendered="New Directions";
if (lastDirections.id!==0 ) { ///checks if last entry is already blank
this.setState({
directions: directions.concat(emptyDirections), //adds empty directions to end and updates currentdirections
currentDirectionsIndex: index
});
}
}
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
render() {
var has_loaded; //has_loaded was added to prevent double rendering during loading of data from WP
this.state.directions.length > 0 ? has_loaded = 1 : has_loaded = 0;
if (has_loaded ) {
/* const currentGrid = this.state.directions;*/
return ( //dummy frame helpful for preventing redirect on form submit
<div>
<div className="fullWidth alignCenter container">
<GridSelector
directions={this.state.directions}
currentDirectionsIndex={this.state.currentDirectionsIndex}
changeTitle={this.changeTitle}
/>
</div>
<Grid ref="grid"
currentGrid={this.state.directions[this.state.currentDirectionsIndex]}
/>
<ImageAdd addImageToGrid={this.addImageToGrid}/>
<div className="fullWidth alignCenter container">
<button onClick={this.createNewDirections.bind(this)}> Create New Directions </button>
</div>
</div>
)
} else {
return(
<div></div>
)
}
}
}
GridSelector.js
class GridSelector extends React.Component {
constructor(props) {
super(props);
var currentDirections = this.props.directions[this.props.currentDirectionsIndex];
this.state = {
currentTitle:currentDirections.title.rendered
}
}
createOption(direction) {
if (direction.title) {
return(
<option key={direction.id}>{direction.title.rendered}</option>
)
} else {
return(
<option></option>
)
}
}
handleChangeEvent(val) {
this.props.changeTitle(val); //triggers parent to update state
}
render() {
return(
<div>
<select name='directions_select'>
{this.props.directions.map(direction => this.createOption(direction))}
</select>
<div className="fullWidth" >
<input
onChange={(e)=>this.handleChangeEvent(e.target.value)}
placeholder={this.state.currentTitle}
id="directionsTitle"
/>
</div>
</div>
)
}
}
Upvotes: 1
Views: 283
Reputation: 2934
You made a very common beginner mistake. In React state should be handled as an immutable object. You're changing the state directly, so there's no way for React to know what has changed. You should use this.setState
.
Change:
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
To something like:
changeTitle(newTitle){
this.setState(({directions,currentDirectionsIndex}) => ({
directions: directions.map((direction,index)=>
index===currentDirectionsIndex? ({...direction,title:{rendered:newTitle}}):direction
})
Upvotes: 3