Building an app and needed to update the data state being rendered in a FlatList. Things were not working as I expected so I broke everything down to a simple example to better understand what was going on.
I feel like I am missing something. I determined from testing in this sample app that Flatlist is looking for an ordered key and has to be exactly ordered starting at 0 and so on…
My question is what am I missing? Is this actually a concrete requirement or do I something configured or coded incorrectly? I have not found any documentation stating this requirement of a very well ordered key.
Here is an example of the issue. If you click on the last item 44, you receive undefined is not an object
as the lookup does not work to the state.
Full code example below;
Post
import React, { Component } from "react";
import { useState } from "react";
import { Button, UIManager, Platform, LayoutAnimation, Modal, Alert, Linking, Pressable, View, Text, StyleSheet, TouchableHighlight, Image, TextInput, FlatList, ActivityIndicator, Dimensions, SafeAreaView, TouchableOpacity, } from "react-native";
import { FlashList } from "@shopify/flash-list";
import { List, ListItem, Avatar } from "react-native-elements";
import { StatusBar } from "expo-status-bar";
import ParsedText from "react-native-parsed-text";
import AntDesign from "react-native-vector-icons/AntDesign";
class Post extends Component {
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() {
console.log(this.props.liked);
return (
<View key={this.props.postid} style={styles.container}>
<TouchableOpacity
onPress={() => this.props.onPressLike(this.props.postid)}
>
<AntDesign
name="like2"
size={20}
color={this.props.liked ? "royalblue" : "black"}
/>
</TouchableOpacity>
<Text> {this.props.postid}</Text>
</View>
);
}
}
Home
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
posts: [
{
postid: "0",
date: "115255668551",
message: "My Post 1 was 4",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xa",
commentCount: "1",
likeCount: "1",
liked: "true",
},
{
postid: "1",
date: "215255668551",
message: "My Post 2",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xb",
commentCount: "1",
likeCount: "1",
liked: "false",
},
{
postid: "2",
date: "315255668551",
message: "My Post 3",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xc",
commentCount: "1",
likeCount: "1",
liked: "true",
},
{
postid: "3",
date: "415255668551",
message: "My Post 4",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xd",
commentCount: "1",
likeCount: "1",
liked: "false",
},
{
postid: "44",
date: "515255668551",
message: "My Post 44 does not work!!!!!!!!!!!!!!!!!",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xe",
commentCount: "1",
likeCount: "1",
liked: "false",
},
],
postsThisIsWorking: [
{
postid: "0",
date: "115255668551",
message: "My Post 0",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xa",
commentCount: "1",
likeCount: "1",
liked: "true",
},
{
postid: "1",
date: "215255668551",
message: "My Post 1",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xb",
commentCount: "1",
likeCount: "1",
liked: "false",
},
{
postid: "2",
date: "315255668551",
message: "My Post 3",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xc",
commentCount: "2",
likeCount: "1",
liked: "true",
},
{
postid: "3",
date: "415255668551",
message: "My Post 3",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xd",
commentCount: "1",
likeCount: "1",
liked: "false",
},
{
postid: "4",
date: "515255668551",
message: "My Post 4",
uid: "52YgRFw4jWhYL5ulK11slBv7e583xe",
commentCount: "1",
likeCount: "1",
liked: "false",
},
],
};
}
renderItem = ({ item, index }) => {
const { date, message, uid, postid, liked } = item;
console.log("---index-----", index);
return (
<Post
postid={postid}
liked={liked}
date={date}
message={message}
uid={uid}
onPressLike={this.handleLikePost}
/>
);
};
handleLikePost = (postid) => {
console.log("----posts----", this.state.posts);
console.log("---postid---", { postid });
let post = this.state.posts[postid]; //error with undefined (evaluating post liked) ----
console.log("----post----", post);
const { liked, likeCount } = post;
const newPost = {
...post,
liked: !liked,
likeCount: liked ? likeCount - 1 : likeCount + 1,
};
this.setState({
posts: {
...this.state.posts,
[postid]: newPost,
},
});
};
render() {
console.log(this.state.posts);
return (
<View style={{ flex: 1 }}>
<FlatList
data={Object.values(this.state.posts)}
renderItem={this.renderItem}
keyExtractor={(item, postid) => postid}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 50,
backgroundColor: "#ecf0f1",
padding: 8,
},
});
3
Answers
I was able to figure out how to properly do the lookup on the object, I needed to use the index which is assigned starting at 0 as the rows are rendered. Works perfectly now...
This is nothing to do with FlatList, or with React for that matter. Your
posts
is defined as an array in the constructor, but you’re trying to access it as an object (this.state.posts[postid]
). (Note thatpostid
contains string values, not numbers, which would have allowed array access to work like that if thepostid
s were always sequential integers starting with zero. Though that seems unlikely in real usage.)To access it like that, your data structure should be
(Later in your code you have a
setState
that also assumesposts
is an object using the above format; it’s only the state data in your constructor that’s in an array.)The initial state of the
posts
is a array. When you’re updating the state inhandleLikePost
you’re setting theposts
to a object.You could do something like this to work with the array. Map over all the posts and modify the post if the id is the same. This will return a new array which you can then set to the state.
I updated the likeCount to parse the int for adding and subtracting. And when returning the new post convert it back to a string.