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

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.


If you are testing on android than try turning off the developer mode. Or are you hitting some API and updating the post on the server and updating the like button in UI corresponding to the server response? If that is the case do tell me, I too have encountered this and I solved it. Also I have commented the second last line in your code which isn't needed.

// 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'
    // You probably don't need the following line.
    // posts[index] = targetPost;

    // Then reset the 'state.posts' property
    this.setState({ posts });
}

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

Tags:

React Native