im trying to render a list and assign my SkillImageTapAction an index to use later but it seems only the last index from the list is saved in each case
This is the flat list
<View style={{ marginTop: 16, marginBottom: 48 }}>
<FlatList
data={matchedHelpers}
renderItem={matchedHelper => renderHelperCell(matchedHelper, props)}
keyExtractor={(matchedHelper: any) => matchedHelper.uid}
numColumns={1}
ListFooterComponent={<ListFooter />}
/>
</View>
And this is the render item
const renderHelperCell = (itemProps, props) => {
const { item } = itemProps;
const { index } = itemProps;
console.log("Index-->",index)
return (
<HelperCell matchedHelper={item} props={props} skillImageTapAction={() => {
setImages(item.matchedSkill.images);
console.log("Index2-->",index)
setTappedIndex(index)
setShowImageSlider(true);
}} />
);
};
Related
Using react native with typescript and redux toolkit
Hi I'm bothering with render a list of messages via FlatList. By ScrollView everything rendering good but I need to implement infiniti scroll. So I'm doing something like this
const MessagesScreen = () => {
const companyId = useAppSelector(getCompanyId);
const userId = useAppSelector(getUserId);
const {
data: messages,
isLoading,
refetch
} = useGetMessagesQuery({ userId, companyId });
useFocusEffect(refetch);
return (
<FlatList
data={messages}
renderItem={() => {
<Messages messages={messages} />;
}}
/>
);
};
In return() I'm trying to render FlatList with component Messages which is down here:
const Messages = ({ messages }: { messages: Message[] }) => {
const navigation =
useNavigation<RootStackScreenProps<'DrawerNavigator'>['navigation']>();
const { colors } = useTheme();
return (
<View style={styles.container}>
{messages.map(message => {
const createdAt = message.created_at;
const isRead = message.read;
const icon = isRead ? 'email-open-outline' : 'email-outline';
const onClick = () => {
navigation.navigate('Message', {
messageId: message.id
});
};
return (
<TouchableOpacity key={message.id} onPress={onClick}>
<View
style={[styles.message, { borderBottomColor: colors.separator }]}
>
<View style={styles.iconPart}>
<Icon
name={icon}
type="material-community"
style={
isRead
? { color: colors.separator }
: { color: colors.inputFocus }
}
size={24}
></Icon>
</View>
<View style={styles.bodyPart}>
<Text
numberOfLines={1}
style={[isRead ? styles.readSubject : styles.unReadSubject]}
>
{message.subject}
</Text>
<Text
numberOfLines={1}
style={[isRead ? styles.readBody : styles.unReadBody]}
>
{message.body}
</Text>
</View>
<View style={styles.datePart}>
<Text style={{ color: colors.shadow }}>
{dayjs(createdAt).fromNow()}
</Text>
</View>
</View>
</TouchableOpacity>
);
})}
</View>
);
};
Actually behaviour is just rendering white screen with error
Possible Unhandled Promise Rejection (id: 17):
Error: Objects are not valid as a React child (found: object with keys {id, msg_type, created_at, subject, body, author, company_id, read}). If you meant to render a collection of children, use an array instead.
there is problem with your call back function:
you are not returning Messages component
1:Remove curly braces
return (
<FlatList
data={messages}
renderItem={() => <Messages messages={messages}/> }
/>
);
2:Add return statement
return (
<FlatList
data={messages}
renderItem={() => {
return <Messages messages={messages} />;
}}
/>
);
Couple things:
You're using the renderItem callback incorrectly:
<FlatList
data={messages}
renderItem={() => {
// ^ ignoring the renderItem props
return <Messages messages={messages} />;
}}
/>
Here, for each item in the messages array, you're rendering a component and passing all the messages into it. So you'll get repeated elements.
The renderItem callback is passed {item, index} where item is the CURRENT item in the array (index is the index into the array)
See docs here:
https://reactnative.dev/docs/flatlist
The usual thing is the renderItem callback renders ONE item at a time, like this:
<FlatList
data={messages}
renderItem={({item}) => {
return <Message message={item} />;
}}
/>
e.g. I'd make a <Message/> component that renders one item only.
I have an accordion inside a flatlist.
Here is the code i have :
const OptionList = (groupOption) => {
return (
<FlatList
data={groupOption.options}
keyExtractor={(result) => result.id.toString()}
renderItem={({ item, index }) => {
const clickedRadio = () => {
const selectedOption = { [item.question]: { ...item } };
setAnswers({ ...answers, ...selectedOption });
};
const status = isOptionSelected(item) ? true : false;
return (
<View key={index}>
<Radio
initialValue={status}
label={item.description}
onChange={() => clickedRadio()}
color="error"
/>
</View>
);
}}
/>
);
};
return (
<View style={styles.container}>
<Text style={{ fontWeight: "bold", fontSize: 16, color:"#6B24AA" }}>
{t("Choose an option/Scroll for more questions")}
</Text>
<FlatList
data={questions}
listKey={(item) => item.id.toString()}
keyExtractor={(result) => result.id.toString()}
renderItem={({ item, index }) => {
const data = [
{
title: item.description,
content: (<><OptionList options=
{item?.question_options}></OptionList></>)
}
];
const status = isOptionSelected(item) ? true : false;
return (
<View style={styles.groupOptions} key={index}>
<Accordion style={styles.accordion}
headerStyle=
{styles.headerStyle} contentStyle={styles.contentStyle}
dataArray={data}
icon={status ? <Icon name="circle"
family="Entypo" size={20} style={{marginLeft: -6,
color: "#6B24AA"}}/>
:
<Icon name="done"
family="MaterialIcons" size={20}
style={{marginLeft: -6}}/>}
expandedIcon={<Icon name="circle"
family="Entypo"/>}
opened={1}/>
</View>
);
}}
/>
</View>
);
The accordion content its anther flatlist component. It shows this error every time i click the accordion.
It shows this error :
VirtualizedList: Encountered an error while measuring a list's offset from its containing VirtualizedList.
at node_modules/react-native/Libraries/Lists/VirtualizedList.js:1411:10 in _scrollRef.measureLayout$argument_2
How can i fix this error? Is it the problem the other flatlist at the content of accordion
Please replace the OptionList component with the given below code.
OptionList
const OptionList = (groupOption) => {
return (
groupOption.map((item,index) => {
const clickedRadio = () => {
const selectedOption = { [item.question]: { ...item } };
setAnswers({ ...answers, ...selectedOption });
};
const status = isOptionSelected(item) ? true : false;
return (
<View key={index}>
<Radio
initialValue={status}
label={item.description}
onChange={clickedRadio}
color="error"
/>
</View>
)
})
);
};
please check and let me know , cheers !
I have a screen in which I display a list of products.
I am trying to set up a pagination. I am using List item from react-native-elements and looking at Using RN FlatList as possible in the documentation for this package.
I set up the ability to do pagination, but I got confused in my code. I don't know how to fix it anymore. I would like to know if it would be possible for you to give me a boost and reread my code to give me your opinion.
There for the moment I have the error:
Each child in a list should have a unique "key" prop
I'm a bit lost and need some guidance please. Thanks for any explanation.
The code :
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {
productId: (props.route.params && props.route.params.productId ? props.route.params.productId : -1),
listData: '',
currentPage: 1,
loadMoreVisible: true,
loadMoreVisibleAtEnd: false,
displayArray: []
}
};
initListData = async () => {
let list = await getProducts(1);
if (list) {
this.setState({
displayArray: list,
loadMoreVisible: (list.length >= 10 ? true : false),
currentPage: 2
});
}
};
setNewData = async (page) => {
let list = await getProducts(parseInt(page));
if (list) {
this.setState({
displayArray: this.state.displayArray.concat(list),
loadMoreVisible: (list.length >= 10 ? true : false),
loadMoreVisibleAtEnd: false,
currentPage: parseInt(page)+1
});
}
};
loadMore() {
this.setNewData(this.state.currentPage);
}
displayBtnLoadMore() {
this.setState({
loadMoreVisibleAtEnd: true
});
}
async UNSAFE_componentWillMount() {
this.initListData();
}
renderItem = ({ item, i }) => (
<ListItem key={i}
bottomDivider
containerStyle={{backgroundColor: i % 2 === 0 ? '#fde3a7' : '#fff' }}
onPress={() => this.props.navigation.navigate('ProductDetails', {productId:parseInt(item.id)})}>
<Icon name='shopping-cart' />
<ListItem.Title style={{width: '65%', fontSize: 14, color: i % 2 === 0 ? '#212121' : '#F78400' }}>{item.name}</ListItem.Title>
<ListItem.Subtitle style={{ color: '#F78400'}}>{i18n.t("information.cost")}:{item.cost}</ListItem.Subtitle>
</ListItem>
);
render() {
//console.log('displayArray', this.state.displayArray)
return (
<View style={{flex: 1}}>
{this.state.displayArray !== null && this.state.displayArray.length > 0 ? (
<View style={{ flex: 1}}>
<SafeAreaView>
{
this.state.displayArray.map((item, i) => (
<FlatList
keyExtractor={(item, i) => {
return item.id;
}}
data={this.state.displayArray}
onEndReached={() => this.displayBtnLoadMore()}
renderItem={this.renderItem}
/>
))
}
</SafeAreaView>
{this.state.loadMoreVisible === true && this.state.loadMoreVisibleAtEnd === true ? (
<Button title=" + " onPress={()=>{this.loadMore()}}></Button>
) : null
}
<View style={styles.container}>
<Text>{"\n"}</Text>
<TouchableOpacity
style={styles.touchable2}
onPress={() => this.props.navigation.goBack()}
>
<View style={styles.container}>
<Button
color="#F78400"
title= 'Back'
onPress={() => this.props.navigation.goBack()}>BACK
</Button>
</View>
</TouchableOpacity>
</View>
<Text>{"\n\n"}</Text>
</View>
) : (
<View style={styles.container}>
<Text>{"\n\n" + (this.state.displayArray === null ? i18n.t("products.searching") : i18n.t("products.nodata")) + "\n\n\n"}</Text>
<Button
color="#F78400"
title= 'Back'
onPress={() => this.props.navigation.goBack()}>BACK
</Button>
</View>
)}
</View>
);
};
}
The problem is not in your list items but in the FlatList itself - you are rendering an array of FlatList components but they don't have unique keys.
this.state.displayArray.map((item, i) => (
<FlatList
key={item.id} // or key={i} if item doesn't have ID
... rest of your flat list props
/>
))
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
i want to create rows for images,which recieve from _find function.This function already seperated array to subarrays,which number equals number of rows,how i can render rows with data from _find?Dont purpose ready solutions such as react-native-easy-grid,i want to do it without another libs,and can i scroll items if i use this way?
import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View,StyleSheet,Button,Image,ScrollView,Dimensions,ListView } from 'react-native';
import Grid from './GridBuilder.js';
const regUrl = /(src=")?(https:\/\/\S{2,500})(")/gm;
var IMAGES_PER_ROW = 3;
let app1;
export default class inputText extends Component {
constructor(props) {
super(props);
app1 = this;
this.state = {
text: null,
findEvent:false,
im_p_r:3,
items:{},
};
}
render() {
return (
<View style={{margin: 20}}>
<TextInput
style = {styles.searchInput}
placeholder="Type here to search"
onChangeText={(text) => this.setState({text})}
/>
<Button
onPress={() => this._find(this.state.text)}s
title='Find'
color="#841584"
accessibilityLabel="on"
/>
{this.state.findEvent && <DisplayImage />}
</View>
);
}
_find(searchText){
fetch('https://www.googleapis.com/customsearch/v1?key=AIzaSyAfcN3jfimFxHxpHNjhHOSuuY8dm5YZnqQ&cx=007223195539364418542:lcqjo0djp7k&num=10&q='+ searchText+'&searchType=image')
.then((resp) => resp.json())
.then(function(data) {
let s = data.items;
let SIZE = IMAGES_PER_ROW;
let res = s.reduce((p,c)=>{
if(p[p.length-1].length == SIZE){
p.link.push([]);
}
p[p.length-1].push(c);
return p.link;
}, [[]])
app1.setState({items:res,findEvent:true});
})
}
}
export class DisplayImage extends Component {
render(){
return(
<View style={styles.container}>
{app1.state.items.map((item,index) => <View style={styles.row} ><Image style={[styles.image,styles.box]} source={{uri:item.link}} key={index} /></View>)}
</View>
)
}
}
const styles = StyleSheet.create({
searchInput:{
fontSize:20,
paddingTop:20,
paddingBottom:20
},
image:{
paddingTop:20,
width:100,
height:100,
},
row: {
flex: 1,
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'space-between'
},
box: {
flex: 1,
height: 100,
width:100,
backgroundColor: '#333',
},
})
AppRegistry.registerComponent('inputText', () => inputText);
AppRegistry.registerComponent('DisplayImage', () => DisplayImage);
You can use FlatList from React Native.
{this.state.findEvent && <FlatList
data={this.state.items}
renderItem={({ item }) => this.renderItem(item)}
/>}
FlatList receive as data the list of elements you want to render, in this case the data returned from the find function.
And then define the renderItem function like:
renderItem(item) {
return (
<View style={styles.row} >
<Image
style={[styles.image,styles.box]}
source={{uri:item.link}} key={index}
/>
</View>
);
}
This function is in charge of rendering the list of images, each image as a row as you want.
FlatList is really useful and makes lists rendering easier. You get the scroll by default and you can also render separators, have a pull to refresh, etc. Take a look to the FlatList doc to see all properties available.
Here is working example of Flat list by which you can get images in the row
https://snack.expo.io/SJDoljDg7
FlatList is the way to go but I suspect the spec has changed since the original accepted answer. You must now provide a key extractor, here is an example of what worked for me:
const listItems = [
{
"id": 0.7967679550647925,
"name": "Hcsrhjkh",
},
{
"id": 0.3212834674770011,
"name": "Phvdgbb",
},
{
"id": 0.30092504022778455,
"name": "Hnvdghbh",
},
]
...
{listItems.length < 1 ? (
<Text style={{ fontSize: 30 }}>Your list is empty.</Text>
) : (
<FlatList
data={listItems}
renderItem={({item}) => <ListItem item={item} />}
keyExtractor={(item) => item.id.toString()}
/>
)}
As you can might have found, the keyExtractor expects a string so I've coerced the 'id' which is a number to a string.