VirtualizedList: missing keys for items - React Native - javascript

On my componentDidMount() I do a Query where I receive some documents from a MongoDb database which I want to show in a FlatList. When I have received the results from the Query I map them to my state.list with the function mapToList() The list.id I use here is an inserted document _id generated by MongoDb itself. Just to have a id to work with here. Then I add key={item.id} to my renderItem({item}) method, but I still get the error that I have to add keys to my VirtualizedList or use a KeyExtractor.
I'm trying to delete an item from the FlatList with the use of Swipeout but first I have to get this id thing to work.
export default class MyFlatList extends React.Component{
state={
loading: false,
list: [{
name: '',
subtitle: '',
rightSubtitle: '',
rightTitle: '',
id: undefined
}]
};
mapToList();
mapToList(result)
{
const list = [];
for(var i = 0; i<result.length; i++)
{
list[i] = {name: result[i].name,
subtitle : result[i].projectname,
rightTitle : result[i].work,
rightSubtitle : result[i].date,
id: result[i]._id
};
}
this.setState({list});
}
render()
render(){
return(
<View>
<FlatList
data={this.state.list}
renderItem={this.renderItem}
/>
<ActivityIndicator animating={this.state.loading} color="black" size="large"/>
</View>
)
}
renderItem({item})
renderItem = ({item}) => (
<Swipeout right={swipeoutBtns}>
<ListItem
title={item.name}
key={item.id}
titleStyle={styles.titleText}
subtitleStyle={styles.subtitleText}
rightTitleStyle={styles.rightSubtitleText}
rightSubtitleStyle={styles.rightSubtitleText}
rightIcon={
<Icon name="keyboard-arrow-right"
size={17}
color="black"
/>}
subtitle={item.subtitle}
rightTitle={item.rightTitle}
rightSubtitle={item.rightSubtitle}
leftAvatar={{rounded:false, title:'PS'}}
/>
</Swipeout>
)

You need keyExtractor parameter. As default it will check if the item has a key property which you don't that why you are getting this warning.
Used to extract a unique key for a given item at the specified index. Key is used for caching and as the react key to track item re-ordering. The default extractor checks item.key, then falls back to using the index, like React does.
Do this:
_keyExtractor = (item, index) => item.id.toString();
<FlatList
data={this.state.list}
renderItem={this.renderItem}
keyExtractor={this._keyExtractor}
/>

Related

"Each child in a list should have unique key" error persists after adding key prop

New to react native, and I know that each list element needs a unique key prop, but I thought I provided one here in the text element. Please guide me in the right direction.
const BadFood = ({ ingredients }) => {
return (
<View>
<Text>Bad Ingredients</Text>
{ingredients.map((ing) => (
<View>
<Text key="{ing}">{ing}</Text>
</View>
))}
</View>
);
};
This is the array I'd like to destructure in firebase BadIngr = ["Peanuts", "Dairy"]
As #jnpdx mentioned change your code as follows:
const BadFood = ({ ingredients }) => {
return (
<View>
<Text>Bad Ingredients</Text>
{ingredients.map((ing) => (
<View key={ing}>
<Text>{ing}</Text>
</View>
))}
</View>
);
};

How to access the first item in an object within an object on Open Library API

Using the Open Library API I am trying to get the first lccn from the array which is 2002044748 which looks something like this.
"first_publish_year": 1937,
"lccn": [
"2002044748",
"67029221",
"2013497341",
"38005859",
"2012545250",
"73008769",
"88156046",
"67000312",
"37038859",
"2007028554",
"84009023",
"2012278060",
"77078707",
"2002075940",
"88009061",
"98102207",
"90156122",
"2001276594",
"2012039220"
],
The lccn - 2002044748 is equal to the book title The Hobbit.
If you will take a closer look on it, you'll be convince that it's an array but when I tried to get the type of it by using typeof lccn it returns an object. Even trying to get the first value like lccn[0] did not work since it's saying it's an object.
What I did is I tried to iterate on the object and see if value will be string since it looks like a string.
Object.entries(lccn).map(item => {
console.log(typeof item);
});
But this returned an object again. I am really confuse on this. How do i get the first item from the lccn array that's actually equates to the title.
Any idea how? I am a newbie when it comes to array.
UPDATE:
I have the following codes on my component:
class BookListingScreen extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
books: [],
errorMessage: '',
isFetching: true,
};
}
async fetchRandomBooks() {
try {
let response = await fetch(
'https://openlibrary.org/search.json?author=tolkien',
);
let json = await response.json();
this.setState({books: json.docs, isFetching: false});
} catch (error) {
this.setState({errorMessage: error});
}
}
componentDidMount() {
this._isMounted = true;
this.fetchRandomBooks();
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
let content = <BookList books={this.state.books} />;
if (this.state.isFetching) {
content = <ActivityIndicator size="large" />;
}
return <View style={styles.container}>{content}</View>;
}
}
Here's the booklist component:
export default class BookList extends Component {
_keyExtractor = item => item.title;
render() {
return (
<FlatList
style={{flex: 1}}
data={this.props.books}
keyExtractor={this._keyExtractor}
renderItem={({item} = this.props.books) => {
return <BookItem item={item} />;
}}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
/>
);
}
}
On my item list props:
export default class BookItem extends Component {
render() {
const {title, lccn} = this.props.item;
console.log(lccn);
return (
<View>
<View style={styles.cardContainerStyle}>
<View style={{paddingRight: 5}}>
{lccn.map(firstLccn => {
return (
<Image
source={{
uri: `https://covers.openlibrary.org/b/lccn/${firstLccn[0]}-M.jpg`,
}}
style={{width: 100, height: 100}}
/>
);
})}
<Text style={styles.cardTextStyle}>{title}</Text>
</View>
</View>
</View>
);
}
}
Basically, I am trying to get the equivalent lccn for each title so I can get the first book cover image as stated on this doc.
It would be data.docs[0].lccn[0]. The JSON you linked to has a docs array where the lccn is in the first element object.
const data = {
numFound: 296,
start: 0,
numFoundExact: true,
docs: [
{
lccn: [
"2002044748",
"67029221",
"2013497341",
"38005859",
"2012545250",
"73008769",
"88156046",
"67000312",
"37038859",
"2007028554",
"84009023",
"2012278060",
"77078707",
"2002075940",
"88009061",
"98102207",
"90156122",
"2001276594",
"2012039220"
]
}
],
num_found: 296,
q: "",
offset: null
};
console.log(data.docs[0].lccn[0]);
Update
TypeError: Cannot read properties of undefined (reading 'map')
at BookItem.render (App.js.js:23:19
The issue in your code is that there are some docs (books) elements that don't have a lccn property. This isn't a problem until in BookItem you attempt to map the lccn array, but you can't since it's undefined.
class BookItem extends React.Component {
render() {
const { title, lccn } = this.props.item;
console.log(lccn); // <-- some undefined values logged
return (
<View>
<View style={styles.cardContainerStyle}>
<View style={{ paddingRight: 5 }}>
{lccn.map((firstLccn) => { // <-- blows up here
return (
...
);
})}
...
</View>
</View>
</View>
);
}
}
See this running Expo Snack for your code and check the console log.
To resolve you can either provide a valid, mappable fallback value when destructuring:
const { title, lccn = [] } = this.props.item;
Or you can use the Optional Chaining operator to guard against the potentially null/undefined value.
{lccn?.map((firstLccn) => {
return (
...
);
})}
Code
class BookItem extends React.Component {
render() {
const { title, lccn } = this.props.item;
console.log(lccn);
return (
<View>
<View style={styles.cardContainerStyle}>
<View style={{ paddingRight: 5 }}>
{lccn?.map((firstLccn) => (
<Image
source={{
uri: `https://covers.openlibrary.org/b/lccn/${firstLccn[0]}-M.jpg`,
}}
style={{ width: 100, height: 100 }}
/>
))}
<Text style={styles.cardTextStyle}>{title}</Text>
</View>
</View>
</View>
);
}
}
If you want to access the first element of an array, you do this:
lccn[0]
The number in the braces is the index of the item. In arrays the first index is 0.
If you want to console.log all elements in an array after eachother, you can do this with the map function.
lccn.map((item)=>{
console.log(item)
})
I saw the lccn you show is an object with key lccn.
SO you can get value by: lccn.lccn[0]
If you want the first element then simply use arr_name[0]. [0] is the index of the very first item.

How to add load more features if my API did not have pagination - react native?

In Home screen, I get data "Songs" from API and save it in the state "Big array".
And I use FlatList to render it, so it rendered fine but I have a too long scroll!
so in data FlatList prop, I slice the array like this
<FlatList
data={songs.slice(0,9)}
...
/>
and it renders 10 songs as expected :D But I want to add Load more when scroll to end
My thought is
add a flag in state like
state={
page: 10
}
<FlatList
data={songs.slice(0,this.state.page)}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0}
...
/>
handleLoadMore = () => {
this.state.songs.length <= this.state.page ? this.setState({page: this.state.page + 10, loading: true}) : null
};
So have any other thoughts How can I handle this case?
Edit
Full FlatList code snippet
// recent Songs FlatList
_renderItem = ({item, index}) => {
const {recent_songs} = this.state;
return (
<TouchableNativeFeed
key={item.id}
onPress={() => {
this.props.saveSongs(recent_songs, index);
this.props.isPlaying(true);
this.props.isPauseTrigger(!this.props.isPauseTrigger);
}}
background={TouchableNativeFeedback.Ripple('white')}
delayPressIn={0}
useForeground>
<Card style={styles.card} noShadow={true}>
<FastImage
source={{uri: item.img}}
resizeMode={FastImage.resizeMode.cover}
style={styles.cardImg}
/>
<Body style={styles.cardItem}>
<View style={styles.radioCardName}>
<View style={styles.cardViewFlex}>
<Text
lineBreakMode="tail"
ellipsizeMode="tail"
numberOfLines={1}
style={styles.text}>
{item.name}
</Text>
</View>
</View>
</Body>
</Card>
</TouchableNativeFeed>
);
};
{/* Recent Songs Here*/}
<View style={{marginVertical: 10}}>
<FlatList
style={{flex: 1}}
horizontal={true}
showsHorizontalScrollIndicator={false}
data={recent_songs.slice(0, 10)}
contentContainerStyle={{flexGrow: 1}}
ListEmptyComponent={<EmptyList />}
keyExtractor={(track, index) => track.id.toString()}
initialNumToRender={10}
renderItem={this._renderItem}
/>
</View>
something like this with flatlist and local pagination
constructor(props) {
super(props);
this.state = {
...
itemPerPage : 10
currentPage : 1, // we keep a track inside the state on each page we are for pagination
songs:[], // contain the songs used by flatlist to render
allSongs : [ ] // contain all the songs returned by api
}
}
async componentDidMount() {
// get the songs from api
let allSongs = await GetSONGS();
this.setState({ allSongs , songs: allSongs.slice(0,this.state.currentPage*this.state.itemPerPage ) });
}
handleLoadMore = async() => {
this.setState({
songs: [ ...this.state.songs , allSongs.slice(this.state.currentPage*this.state.itemPerPage,(this.state.currentPage+1)*this.state.itemPerPage ) ] , // concat the old and new data together
currentPage : this.state.currentPage +1
})
}
render(){
return(
<FlatList
data={this.state.songs}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={10} // how many item to display first
onEndReachedThreshold={5} // so when you are at 5 pixel from the bottom react run onEndReached function
onEndReached={() => {
this.handleLoadMore();
}}
/>
)
}
anyway i use RecyclerListView in a previous project to render list of 10 000 elements

How do I toggle between items in a FlatList?

I added a custom radio button to each of my flatlist items. So there's a radio button on the left of each item row and I want to be able to toggle between the selected value of the radio button depending on row item. So, I can select the first row item and that's selected/toggled on or select the second one item and that's toggled on and the first row item is toggled off.
I'm a bit confused on how to set up the logic for this with my current object. My current object has an id and isSelected bool. The bool will indicate if the row is isSelected or not (i.e. is checked or not).
Here's what i have so far:
This is my component that renders the flatlist fooItems. The fooItems itself is the individual elements that make up the list.
export class FooItemsComponent extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
//some reducer action
}
renderItem = item => {
if (item.load) {
return <ActivityIndicator color={Colors.activityColor} size="small" />;
} else {
return (
<View>
<FooItem
{...item}
navigation={this.props.navigation}
showParticularItems={true}
/>
</View>
); }
};
render() {
const { fooItems, navigation, loading, props} = this.props;
if (!fooItems) fooItems = [];
return (
<View style={styles.container}>
<FlatList
keyExtractor={(item, index) => `key-${index}`}
data={fooItems}
renderItem={item => this.renderItem(fooItems.item)}
ItemSeparatorComponent={Separator}
ListHeaderComponent={Header}
ListFooterComponent={loading ? LoadingFooter : Separator}
/>
</View>
);
}
}
FooItem component:
export default class FooItem extends Component {
constructor(props) {
super(props);
this.state = {
isSelected: this.props.fooItems.isSelected
};
}
changeSelected = isSelected => {
if(!isSelected) {
let selected = this.props.fooItems;
selected = selected.isSelected = true;
this.setState({ isSelected: selected});
}
};
render() {
const {
Details,
showPreferred = false,
showSetSelected = false,
navigation
} = this.props;
const { isSelected} = this.state;
return (
<View
style={[ showSetSelected? styles.setDefaultContainer :
styles.container,
showBorder ? { borderBottomWidth: 1, borderTopWidth: 1 } : {},
]}
>
{showSetSelected && (
<View>
<TouchableOpacity
style={[styles.info, !isSelected ? styles.lineBreak : {}]}
onPress={() => this.changeSelected(isSelected)}
>
<RadioButton isChecked={isSelected}></CheckedCircle>
</TouchableOpacity>
</View>
)}
</View>
);
}
}
My custom control:
const RadioButton = ({ isChecked }) => {
let checkedStatus;
switch (isChecked) {
case true:
checkedStatus = <Image style={styles.container} source={require('../../../assets/radioButton.png')} />
break
default:
checkedStatus = <Image style={styles.container} source={require('../../../assets/circle.png')} />
break
}
return <View style={styles.container}>{checkedStatus}</View>
}
Right now, I'm noticing the other row that is not already selected and I select it, the radio button state for that particular row is checked but now I have two checked/selected rows instead of one. So I want to know how to toggle the other row. How can I get this done?
EDIT:
Example of radio button toggle in a list where I only want to toggle between listed radio buttons between rows of the list.
Maintain a state variable such as selectedIndex or some unique id in your FooItemsComponent and pass this index/id to each of your FooItem and compare against this selectedIndex as
<RadioButton
isChecked={item.id === this.props.selectedIndex}
/>
Maintain a method at FooItemsComponent level which controls which item is checked as follows
changeSelected = index => {
this.setState({
selectedIndex: index,
});
};
and pass the same to every FooItem
<FooItem
{...item}
...
selectedIndex={this.state.selectedIndex}
changeSelected={this.changeSelected}
/>
and finally in the FooItem put this on a Touchable and pass the FooItem's id or index.
<TouchableOpacity onPress={() => this.props.changeSelected(item.id)}>
Here is a expo link for rough idea.(Please ignore the styles and make it truly yours!)
You need to keep track of which FooItem is selected in the state of your FooItemsComponent.
Then use that to check
<FooItem
{...item}
isSelected={this.state.selectedItem.id === item.id}
navigation={this.props.navigation}
showParticularItems={true}
/>
Or something in this fashion.

How to display a single dimensional array in FlatList

You would think that this would be the easiest thing in the world to find an example of but everthing I found online has always had a JSON file as the data source so there are keys to reference
stuff like this...eg item.name etc
const list = [
{
name: 'Amy Farha',
subtitle: 'Vice President'
},
{
name: 'Chris Jackson',
avatar_url: 'https://s3.amazonaws.com/uifaces/faces/twitter/adhamdannaway/128.jpg',
subtitle: 'Vice Chairman'
},
... // more items
]
keyExtractor = (item, index) => index.toString()
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.subtitle}
leftAvatar={{
source: item.avatar_url && { uri: item.avatar_url },
title: item.name[0]
}}
/>
)
My data source is just a plain single dimension array, no keys or identifiers of any kind. The data is what it is and wont be changed
data = ['value1','value2','value3',...]
The code I have so far (which obviously doesnt work) is
import React, { Component } from 'react'
import { FlatList, ListItem } from 'react-native'
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
key={item.id}
/>
)}
/>
Im guessing I need to use the keyExtractor somewhere to generate an id but how do I reference the actual values in the data to display them if it doesnt have an identifier in the data like a json would?
Try this:
<FlatList
data={this.state.data}
keyExtractor={item => item}
renderItem={({ item }) => (
<ListItem
title={item}
/>
)}
/>
const ListItem = ({ title }) => (
<View>
<Text>{title}</Text>
...
</View>
);

Categories