0
votes

The part of my app in question is utilizing 4 components, 1 parent and 3 children. As expected, the parent component handles all state and passes values to child components via props.

The parent component contains methods for updating state changes, also passed down via props. The child components do not have state other than component methods, they use only props for visual data.

The problem I’m having is that when I call the method to update parent state, the parent state is updated successfully (verified via the console), however the child component, which reflects this state via its props, doesn’t render the state change visually.

The code is below which I’ll do my best to explain:

Parent component update state method:

handleUpvoteState(blurtIndex) {
    let blurts = [...this.state.followingBlurts],
        updatedBlurt = {...blurts[blurtIndex]};
    updatedBlurt.vote++;
    blurts[blurtIndex] = updatedBlurt;
    this.setState({
        followingBlurts: blurts
    });
    console.log(this.state.followingBlurts[blurtIndex].vote); // State change reflected.
}

Parent component passing state to child props:

<LazyBlurtsPanel
     appendDate={this.state.appendDate}
     random={this.state.randomize} 
     blurts={this.state.followingBlurts} 
     handleLazyState={this.handleLazyState}
     handleUpvoteState={this.handleUpvoteState}
     lazyElement='lzyfollowing'
     apiMethod={this.state.apiMethod}
     currentUser={false}
 />

The lazy panel component (above) then sends data to footer child via props:

<BlurtFooter 
   voteCount={this.props.blurts[i].vote}
   currentUser={this.props.currentUser}
   blurtId={this.props.blurts[i]._id}
   blurtIndex={i}
   handleUpvoteState={this.props.handleUpvoteState}
 />

When I call

this.props.handleUpvoteState(index)

in my child footer component, the parent state is successfully updated. However, the footer component doesn't re-render to reflect the parent's updated state.

Here's the footer component code that isn't getting re-rendered:

render() {
    return (
        <div className="blurt-panel-footer">
           <UpvoteCount voteCount={this.props.voteCount}/>

Any help much appreciated.

EDIT: There was some confusion as to how blurtfooter is called, so I'm including the code for how the panel is built inside a loop within LazyBlurtPanel component. Here's the code for that:

for(let i=numRendered; i<renderCount; i++) {
  if (this.props.blurts[i]) {
    renderBlurts.push(
         <div className='blurt-panel' id={(((i + 1) % 6) === 0) ? this.props.lazyElement : 'false'} key={i}>
              <BlurtHeader blurt={this.props.blurts[i]} />
              <div className='blurt-panel-body'>
                  <Sanitizer dirty={ this.props.blurts[i].text } />
                  {
                      (this.props.blurts[i].blurtImg !== 'false')? <img src={'/images/blurt/' + this.props.blurts[i].blurtImg} /> : ''
                  }
              </div>
              <BlurtFooter 
                  voteCount={this.props.blurts[i].vote}
                  currentUser={this.props.currentUser}
                  blurtId={this.props.blurts[i]._id}
                  blurtIndex={i}
                  handleUpvoteState={this.props.handleUpvoteState}
                  key={this.props.blurts[i]._id} //Tried with and without key here..
              />
          </div>
      );
  }
}
2
let blurts = JSON.parse(JSON.stringify(this.state.followingBlurts)); try this.sk01
@sk01 my initial state sets followingBlurts to an array... Why would I switch to object notation?? As mentioned in my question, the state actually updates without issue. Thanks though!silencedogood
the state change is asynchronous. if it was working properly, the state change should not be reflected in console. The problem here is when you use spread operator, the array reference is same. So you are directly making change to that array. Json stringify will solve the problem.sk01
@sk01 I was a bit misleading in my description. The state change is reflected in the console, but it's always one number behind (due to async behavior you mentioned, good observation btw), nonetheless I can verify it is updating the state after the second button press (upvote). And I'm making changes directly to the array only, which is a shallow copy of state array... Just tried your solution for the sake of saking, no luck my friend.silencedogood
Try adding a console.log in the render() method of your LazyBlurtsPanel to see if it is being rerendered after the state change. If I'm right you are rendering BlurtFooter in a loop, then you shoud see a warning about missing key property. Not sure if it can cause this problem, but try adding key={this.props.blurts[i]._id}Dimitri L.

2 Answers

1
votes

Try changing how BlurtFooter is called. Even if this doesn't fix it it will still be more maintainable code

render() {
  const { currentUser, blurts, lazyElement, handleUpvoteState } = this.props;
  return (
    <Fragment>
      {
        blurts.filter(x => x).map((blurt, index) => (
          <div className="blurt-panel" id={(((index + 1) % 6) === 0) ? lazyElement : 'false'} key={blurt._id}>
              <BlurtHeader blurt={blurt} />
              <div className="blurt-panel-body">
                  <Sanitizer dirty={blurt.text} />
                  {
                      blurt.blurtImg !== 'false' &&
                      <img src={`/images/blurt/${blurt.blurtImg}`} />
                  }
              </div>
              <BlurtFooter 
                  voteCount={blurt.vote}
                  currentUser={currentUser}
                  blurtId={blurt._id}
                  blurtIndex={index}
                  handleUpvoteState={handleUpvoteState}
              />
          </div>
        )
      }
    </Fragment>
  );
}

There are a lot of other improvements that can be made, but see how that works, especially setting key correctly is a must.

I'm not sure what that id is on the div but that looks fishy as well and shouldn't be there.

Also the part blurts.filter(x => x) shouldn't be needed, so state should be improved to not need that

Updated: Make sure to take latest code above

0
votes

State should always update in a nonmutating way. This way react core library could understand there is a change in data. Array and objects use reference pointers in memory. So even if we update a value inside those reffrence type variables it won't affect the original memory location of those variables. So create a new variable in memory and copy the state that you need to update and then set the state by using the new variable. This way we can avoid this issue. Also you can create a nonmutating structure using modern es6 + methods.