So, as far as I understand react only rerenders new elements with new keys. Thats not working for me though. I have a list of posts, that are limited to 3. When the user scrolls to bottom of page I add 3 to the limit, which means at the bottom of the page 3 older posts are supposed to be shown. What I have now works, but the entire list is being rerendered. And it jumps to the top which is also not wanted (this I can fix though, main problem is the rerendering). They all have unique keys. How can I prevent this behaviour?
thisGetsCalledWhenANewPostComesIn(newPost){
let newPosts = _.clone(this.state.posts);
newPosts.push(newPost);
newPosts.sort((a,b) => b.time_posted - a.time_posted);
this.setState({posts: newPosts});
}
render(){
return (
<div ref={ref => {this.timelineRef = ref;}} style={styles.container}>
{this.state.posts.map(post =>
<Post key={post.id} post={post} />
)}
</div>
);
}
So, as far as I understand react only rerenders new elements with new keys. Thats not working for me though. I have a list of posts, that are limited to 3. When the user scrolls to bottom of page I add 3 to the limit, which means at the bottom of the page 3 older posts are supposed to be shown. What I have now works, but the entire list is being rerendered. And it jumps to the top which is also not wanted (this I can fix though, main problem is the rerendering). They all have unique keys. How can I prevent this behaviour?
thisGetsCalledWhenANewPostComesIn(newPost){
let newPosts = _.clone(this.state.posts);
newPosts.push(newPost);
newPosts.sort((a,b) => b.time_posted - a.time_posted);
this.setState({posts: newPosts});
}
render(){
return (
<div ref={ref => {this.timelineRef = ref;}} style={styles.container}>
{this.state.posts.map(post =>
<Post key={post.id} post={post} />
)}
</div>
);
}
Share
Improve this question
edited Dec 16, 2016 at 14:48
ArneHugo
6,5392 gold badges28 silver badges51 bronze badges
asked Dec 16, 2016 at 13:54
ThatBrianDudeThatBrianDude
3,2003 gold badges22 silver badges45 bronze badges
3 Answers
Reset to default 7Having unique keys alone does not prevent rerendering ponents that have not changed. Unless you extend PureComponent or implement shouldComponentUpdate for the ponents, React will have to render()
the ponent and pare it to the last result.
So why do we need keys when it's really about shouldComponentUpdate
?
The purpose of giving each ponent in a list a unique key is to pass the props to the "right" ponent instances, so that they can correctly pare new and old props.
Imagine we have a list of items, e.g.:
- A -> ponentInstanceA
- B -> ponentInstanceB
- C -> ponentInstanceC
After applying a filter, the list must be rerendered to show the new list of ponents, e.g.:
- C -> ?
Without proper unique keys, the ponent that previously rendered A
will now receive the prop(s) for C
. Even if C
is unchanged, the ponent will have to rerender as it received pletely different data:
- C -> ponentInstanceA // OH NO!
With proper unique keys, the ponent that rendered C
will receive C
again. shouldComponentUpdate
will then be able to recogize that the render() output will be the same, and the ponent will not have to rerender:
- C -> ponentInstanceC
If your list of items take a long time to render, e.g. if it's a long list or each element is a plex set of data, then you will benefit from preventing unnecessary rerendering.
Personal anecdote
In a project with a list of 100s of items which each produced 1000s of DOM elements, changing from
list.map((item, index) => <SomeComp key={index} ... />)
to
list.map(item => <SomeComp key={item.id} ... />)
reduced the rendering time by several seconds. Never use array index as key.
You will have to implement shouldComponentUpdate(nextProps, nextState) in the Post
ponent. Consider extending the PureComponent class for the Post
ponent instead of the default React Component
.
Good luck!
PS: you can use a string as ref parameter for your div in the render method like so:
render() {
return (
<div
ref='myRef'
style={styles.container}
>
{this.getPostViews()}
</div>
);
}
Then, if you want to refer to this element, use it like this.refs.myRef
. Anyway, this is just a personal preference.
Okay, my bad. I thought I'd only post the "relevant" code, however it turns out, the problem was in the code I left out:
this.setState({posts: []}, ()=> {
this.postListenerRef = pletedPostsRef.orderByChild('time')
.startAt(newProps.filter.fromDate.getTime())
.endAt(newProps.filter.toDate.getTime())
.limitToLast(this.props.filter.postCount)
.on('child_added', snap => {
Database.fetchPostFromKey(snap.key)
.then(post => {
let newPosts = _.clone(this.state.posts);
newPosts.push(_.assign(post, {id: snap.key}));
newPosts.sort((a,b) => b.time_posted - a.time_posted);
this.setState({posts: newPosts});
}).catch(err => {throw err;});
});
});
I call setState({posts: []})
which I am 99% sure is the problem.