最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React not re-rendering after a change in state, how do I setState of child components? - Stack Overflow

programmeradmin1浏览0评论

Problem:

I have a collection of Legislators put into a table, the field headers are buttons and have click events attached to them that sort the collection by the field button clicked.

render() {
    const legislatorList = this.populateList() //  creates an Array of Legislator ponents
    return (
      <div className="legislators">
        <h2>Legislators</h2>
        <table className="table table-bordered">
        <thead>
          <tr>
            <th><button type="button" onClick={ () => this.handleChange("first_name")}  >First</button></th>
            <th><button type="button" onClick={ () => this.handleChange("last_name")}  >Last</button></th>
            <th><button type="button" onClick={ () => this.handleChange("state")}  >State</button></th>
            <th><button type="button" onClick={ () => this.handleChange("party")}  >Party</button></th>
          </tr>
        </thead>
          <tbody>
            { legislatorList }
          </tbody>
        </table>
      </div>
    );

The click event re-assigns this.state.legislators to the output of the a sort function.

handleChange( value ) {
    this.setState( { legislators: this.sort_by( value) })
  }

But the table remains the same even though the updated state of state.legislators is correctly sorted.

I think my problem is that the child Legislator ponents of Legislators is not updating its state. So my question is how do you update the state of child ponents?

Before Any Sorting:

After Sorting by First Name:

Other Functions of Legislators:

sort_by(sort_by) {
  const sorted = this.state.legislators.sort(function(a, b) {
    a_val = a[sort_by]
    b_val = b[sort_by]
    if (a_val < b_val) {
      return -1;
    }
    if (a_val > b_val) {
      return 1;
    }
    return 0;
  });
  return sorted
}

renderLegislator(legislator) {
  return <Legislator legislator={ legislator}/>
}

populateList() {
  return this.state.legislators.map((legislator) =>
    this.renderLegislator(legislator)
  )}
}

Here is the Legislator ponent:

class Legislator extends React.Component {
  constructor(props) {
    super();
    this.state = {
      legislator: props.legislator,
      fields: ["first_name", "last_name", "state", "party"]
    }
  }

  render() {
    const legislatorRow = this.state.fields.map((field) =>
        <LegislatorField value={ this.state.legislator[field] } />
    )
    return (
      <tr key={this.state.legislator.id}>
        { legislatorRow }      
      </tr>
    );
  }
}

Problem:

I have a collection of Legislators put into a table, the field headers are buttons and have click events attached to them that sort the collection by the field button clicked.

render() {
    const legislatorList = this.populateList() //  creates an Array of Legislator ponents
    return (
      <div className="legislators">
        <h2>Legislators</h2>
        <table className="table table-bordered">
        <thead>
          <tr>
            <th><button type="button" onClick={ () => this.handleChange("first_name")}  >First</button></th>
            <th><button type="button" onClick={ () => this.handleChange("last_name")}  >Last</button></th>
            <th><button type="button" onClick={ () => this.handleChange("state")}  >State</button></th>
            <th><button type="button" onClick={ () => this.handleChange("party")}  >Party</button></th>
          </tr>
        </thead>
          <tbody>
            { legislatorList }
          </tbody>
        </table>
      </div>
    );

The click event re-assigns this.state.legislators to the output of the a sort function.

handleChange( value ) {
    this.setState( { legislators: this.sort_by( value) })
  }

But the table remains the same even though the updated state of state.legislators is correctly sorted.

I think my problem is that the child Legislator ponents of Legislators is not updating its state. So my question is how do you update the state of child ponents?

Before Any Sorting:

After Sorting by First Name:

Other Functions of Legislators:

sort_by(sort_by) {
  const sorted = this.state.legislators.sort(function(a, b) {
    a_val = a[sort_by]
    b_val = b[sort_by]
    if (a_val < b_val) {
      return -1;
    }
    if (a_val > b_val) {
      return 1;
    }
    return 0;
  });
  return sorted
}

renderLegislator(legislator) {
  return <Legislator legislator={ legislator}/>
}

populateList() {
  return this.state.legislators.map((legislator) =>
    this.renderLegislator(legislator)
  )}
}

Here is the Legislator ponent:

class Legislator extends React.Component {
  constructor(props) {
    super();
    this.state = {
      legislator: props.legislator,
      fields: ["first_name", "last_name", "state", "party"]
    }
  }

  render() {
    const legislatorRow = this.state.fields.map((field) =>
        <LegislatorField value={ this.state.legislator[field] } />
    )
    return (
      <tr key={this.state.legislator.id}>
        { legislatorRow }      
      </tr>
    );
  }
}
Share Improve this question edited Feb 1, 2017 at 2:17 Mitch Kroska asked Feb 1, 2017 at 1:28 Mitch KroskaMitch Kroska 3254 silver badges15 bronze badges 2
  • Please paste the content of populateList() and sort_by() as well – IzumiSy Commented Feb 1, 2017 at 1:32
  • Try to put console.log in the callback function of the second parameter of setState or in ponentDidUpdate function to check out if the ponent is really updated. – IzumiSy Commented Feb 1, 2017 at 2:40
Add a ment  | 

2 Answers 2

Reset to default 5

The state.legislator in your child ponent is not being updated because the code:

this.state = {
  legislator: props.legislator,
  fields: ["first_name", "last_name", "state", "party"]
}

only runs once as the constructor only runs once, too. And the state.legislator in child ponent won't automatically update unless you do this.setState() explicitly inside the child ponent.

However, there is no need to do in such a way you are doing. Maintaining own state for props in the child ponent is absolutely redundant.

You can directly use this.props inside render method in child ponent, and when the state changes in the parent ponent, the child will re-render with new props.

 class Legislator extends React.Component {
  constructor(props) {
    super();
    this.state = {
      fields: ["first_name", "last_name", "state", "party"]
    }
  }

  render() {
    const legislatorRow = this.state.fields.map((field) =>
        <LegislatorField value={ this.props.legislator[field] } />
    )
    return (
      <tr key={this.state.legislator.id}>
        { legislatorRow }      
      </tr>
    );
  }
}

As a bonus, if your this.state.fields is always constant -- you don't intend to update it later, you do not have to have it as a state either. You can simply make it a class instance field as this.fields = ["first_name", "last_name", "state", "party"]

The main problem is that Array.prototype.sort mutates the array and returns it, instead of returning a new array sorted.

 a = [4,1,2,3];
 b = a.sort();
 a === b // true , they are the same object

So when you "change the state" in handleChange you're actually mutating the state assigning the same object reference, making it difficult to React to tell that you've made any changes at all.

The easy way out would be to use Immutable to store the collection of legislators. Using Immutable.List.sort you will have a new array with the new value, instead of the old array mutated.

Other way out would be to define your ponent more clearly by having a sortBy property on the state that contains the key to sort the list by, and updating that instead of the whole collection.

constructor

this.state = {
  legislator: props.legislator,
  fields: ["first_name", "last_name", "state", "party"],
  sortBy: 'first_name'
}

handleChange

handleChange( value ) {
  this.setState( { sortBy: value })
}

populateList

populateList() {
  return this.state.legislators.map((legislator) =>
    this.renderLegislator(legislator)
  ).sort(function(a, b) {
     a_val = a[this.state.sortBy]
     b_val = b[this.state.sortBy]
     if (a_val < b_val) {
       return -1;
     }
     if (a_val > b_val) {
       return 1;
     }
     return 0;
   });
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论