However, these functions need to be defined inside the Posts component because they are changing state of the Posts component. How do I update the state of the right post?
… or forward the index to the Post component, so that it can for example set it as a data-* attribute on a native element; thus avoiding inline function definitions inside the render method:
On another note, you shouldn’t .sort() the posts inside the render method as this mutates the state. Instead, you should always use setState() and do so somewhere else (e.g. inside the constructor and then after having incremented a rating).
When I sort the posts inside the constructor I get the error message “Warning: Can’t call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Posts component.” inside the browser devtools console.
I think votes should be a property of the posts, not another state property next to posts… here’s how you’d do this without mutating any data (using the spread syntax):
// Create a copy of the posts
const posts = [...this.state.posts]
// Don't mutate the target post either, but replace it
// with a copy with an updated votes property
const current = posts[index]
posts[index] = { ...current, votes: current.votes + 1 }
// Now the posts can be sorted without worries
posts.sort((a, b) => a.votes - b.votes)
this.setState({ posts })
Yes, sorry… the constructor is indeed the only place where you can (and must) set the state directly.
By calling setState(), the component will re-render either way; however, if you were to implement shouldComponentUpdate() (or using a pure component), you couldn’t reliably compare the current with the next state as you already mutated the current state. For example, this would always return false:
No, but because the state gets updated asynchronously you can’t reliably compute the next state from the current state; that’s why there’s a callback version of setState(), which takes the previous state as its first argument:
// To use the classic example...
this.setState(prevState => ({
counter: prevState.counter + 1
}))
In this case, you can be sure that prevState holds the state from when you called setState(), regardless of any updates that may have happened in the meanwhile. (This will not protect you against direct mutations of the state though!)