1

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>
        )
    }
}

1 Answer 1

3

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
    })
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.