Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update a single item in FlatList in React Native?

Tags:

react-native

Attention: I have posted an answer down there, personally I think it's the best solution so far. Even though it's not the highest rated answer, but based on the result I'm getting, it is very efficient.

---------------------------------------------Original Question-------------------------------------------------------

Suppose I am writing a Twitter clone, but much simpler. I put each item in FlatList and render them.

To "like" a post, I press the "like" button on the post and the "like" button turns red, I press it again, it turns gray.

This is what I have so far: I store all the loaded posts in this.state, each post has a property called "liked", which is boolean, indicating whether this user has liked this post or not, when user presses "like", I go to state.posts and update the liked property of that post, and then use this.setState to update posts like so:

// 1. FlatList <FlatList     ...     data={this.state.posts}     renderItem={this.renderPost}     ... />   // 2. renderPost renderPost({ item, index }) {     return (         <View style={someStyle}>             ... // display other properties of the post             // Then display the "like" button             <Icon                 name='favorite'                 size={25}                 color={item.liked ? 'red' : 'gray'}                 containerStyle={someStyle}                 iconStyle={someStyle}                 onPress={() => this.onLikePost({ item, index })}             />             ...         </View>     ); }  // 3. onLikePost likePost({ item, index }) {     let { posts } = this.state;     let targetPost = posts[index];      // Flip the 'liked' property of the targetPost     targetPost.liked = !targetPost.liked;      // Then update targetPost in 'posts'     posts[index] = targetPost;      // Then reset the 'state.posts' property     this.setState({ posts }); } 

This approach works, however, it is too slow. The color of the "like" button flips as I press it, but it usually takes about 1 second before the color changes. What I want is that the color would flip almost at the same time when I press it.

I do know why this would happen, I should probably not use this.setState, because when I do that, the posts state changed, and all posts get re-rendered, but what other approach can I try?

like image 266
K.Wu Avatar asked Oct 28 '17 20:10

K.Wu


People also ask

How do you update data in FlatList in React Native?

Since FlatList is a PureComponent , it assumes that the data is an immutable object and that any change in data is done by setting a new data source object in the state object. Alternatively, use the extraData={this. state. isChanged} prop to re-render a FlatList component by updating the value of isChanged property.

How do you edit items in React Native?

Typically you'll want an edit callback handler that takes the index or id of the data element to edit (in order to toggle some edit view), the edit mode view (i.e. some input), and a save edit handler to update that element in your data in state.

How do I scroll to particular item in FlatList React Native?

If you are using a FlatList in React Native, there's a chance you will want to use the scrollToIndex function to scroll to a certain item's index. Unfortunately, this function is a lot easier to use with fixed item heights where it is easy to implement the required getItemLayout function.

How do I pass extra data on FlatList?

flatlist-simple By passing extraData={selectedId} to FlatList we make sure FlatList itself will re-render when the state changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is a PureComponent and the prop comparison will not show any changes.


2 Answers

You can set extraData in FlatList:

<FlatList ...     extraData={this.state}     data={this.state.posts}     renderItem={this.renderPost}     ... />  

When state.posts or state.posts's item change, FlatList will re-render.

From FlatList#extradata:

A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.

Update:

Functional component implementation:

export default function() {     // list of your data     const [list, setList] = React.useState([])     const [extraData, setExtraData] = React.useState(new Date())      // some update on the item of list[idx]     const someAction = (idx)=>{         list[idx].show = 1         setList(list)         setExtraData(new Date())     }     return (         <FlatList             // ...             data={list}             extraData={extraData}         />     ) }  

After updating list, I use setExtraData(new Date()) to tell the FlatList to re-render. Because the new time is different from the previous.

like image 98
tomfriwel Avatar answered Sep 28 '22 03:09

tomfriwel


Don't get me wrong, @ShubhnikSingh's answer did help, but I retracted it because I found a better solution to this question, long time ago, and finally I remembered to post it here.

Suppose my post item contains these properties:

{     postId: "-L84e-aHwBedm1FHhcqv",     date: 1525566855,     message: "My Post",     uid: "52YgRFw4jWhYL5ulK11slBv7e583",     liked: false,     likeCount: 0,     commentCount: 0 } 

Where liked represents whether the user viewing this post has liked this post, which will determine the color of the "like" button (by default, it's gray, but red if liked == true)


Here are the steps to recreate my solution: make "Post" a Component and render it in a FlatList. You can use React's PureComponent if you don't have any props that you pass to your Post such as an array or object that can be deceptively not shallow equal. If you don't know what that means, just use a regular Component and override shouldComponentUpdate as we do below.

class Post extends Component {                                                         // This determines whether a rendered post should get updated                        // Look at the states here, what could be changing as time goes by?                  // Only 2 properties: "liked" and "likeCount", if the person seeing                  // this post ever presses the "like" button                                          // This assumes that, unlike Twitter, updates do not come from other                 // instances of the application in real time.                                        shouldComponentUpdate(nextProps, nextState) {                                          const { liked, likeCount } = nextProps                                               const { liked: oldLiked, likeCount: oldLikeCount } = this.props                       // If "liked" or "likeCount" is different, then update                               return liked !== oldLiked || likeCount !== oldLikeCount                            }                                                                                     render() {                                                                             return (                                                                               <View>                                                                                 {/* ...render other properties */}                                                   <TouchableOpacity                                                                      onPress={() => this.props.onPressLike(this.props.postId)}                          >                                                                                      <Icon name="heart" color={this.props.liked ? 'gray' : 'red'} />                    </TouchableOpacity>                                                                </View>                                                                            )                                                                                  }                                                                                  }                                                                                    

Then, create a PostList component that will be in charge of handling the logic for loading posts and handling like interactions:

class PostList extends Component {                                                          /**                                                                                         * As you can see, we are not storing "posts" as an array. Instead,                         * we make it a JSON object. This allows us to access a post more concisely                 * than if we stores posts as an array. For example:                                        *                                                                                          * this.state.posts as an array                                                             * findPost(postId) {                                                                       *   return this.state.posts.find(post => post.id === postId)                               * }                                                                                        * findPost(postId) {                                                                       *   return this.state.posts[postId]                                                        * }                                                                                        * a specific post by its "postId", you won't have to iterate                               * through the whole array, you can just call "posts[postId]"                               * to access it immediately:                                                                * "posts": {                                                                               *     "<post_id_1>": { "message": "", "uid": "", ... },                                    *     "<post_id_2>": { "message": "", "uid": "", ... },                                    *     "<post_id_3>": { "message": "", "uid": "", ... }                                     * }                                                                                        * FlatList wants an array for its data property rather than an object,                     * so we need to pass data={Object.values(this.state.posts)} rather than                    * just data={this.state.posts} as one might expect.                                       */                                                                                            state = {                                                                                      posts: {}                                                                                    // Other states                                                                            }                                                                                             renderItem = ({ item }) => {     const { date, message, uid, postId, other, props, here } = item     return (       <Post         date={date}         message={message}         uid={uid}         onPressLike={this.handleLikePost}       />     )   }    handleLikePost = postId => {     let post = this.state.posts[postId]     const { liked, likeCount } = post      const newPost = {       ...post,       liked: !liked,       likeCount: liked ? likeCount - 1 : likeCount + 1     }      this.setState({       posts: {         ...this.state.posts,         [postId]: newPost       }     })   }    render() {     return (       <View style={{ flex: 1 }}>         <FlatList           data={Object.values(this.state.posts)}           renderItem={this.renderItem}           keyExtractor={({ item }) => item.postId}         />       </View>     )   } } 

In summary:

1) Write a custom component (Post) for rendering each item in "FlatList"

2) Override the "shouldComponentUpdate" of the custom component (Post) function to tell the component when to update

Handle the "state of likes" in a parent component (PostList) and pass data down to each child

like image 31
K.Wu Avatar answered Sep 28 '22 04:09

K.Wu