I have a listing component in my react native map. in which I'm mapping the list of items in the component. How the problem is. I have been getting warnings that each child should have a unique key. Even though I have given them a unique key but still getting that warning.
here is the code.
<ScrollView horizontal>
{this.state.isLoading === true ? (
[...Array(7)].map((item, index) => <PremiumShimmer key={index} />)
) : this.state.isError === true ? (
<View>
<Text style={{ color: "black" }}>
Facing some issue to load data from server
</Text>
</View>
) : this.state.Data.length === 0 ? (
<Text style={{ color: "black" }}>No Data Founbd</Text>
) : (
this.state.Data.map((item) => (
this.ItemRenderer({item})
))
)}
</ScrollView>
ItemRenderer ({ item }) {
return (
<PremiumItemListComponent item={item} navigation={this.props.navigation} />
);
};
PremiumItemListComponent
<TouchableOpacity
style={styles.listBox}
onPress={() => this.props.navigation.navigate("Detail", { data: this.props.item })}
>
<ImageBackground style={styles.listImage} imageStyle={{ borderRadius: 26}} source={{uri:SERVER_URL.apiUrl+this.props.item.background}}>
<Text style={{color:"white",fontSize:13,paddingLeft:10,fontWeight: 'bold'}}>{this.props.item.name}</Text>
<View style={{ flexDirection: 'row', flexWrap: 'wrap',padding:10}}>
{
[...Array(5)].map((e, i) =>
// 3.688689 will be replaced by rating
// console.log(typeof i,typeof 4, i+1<=4)
<Ionicons name={i+1<=Number((3.688689).toFixed(0))?"star-sharp":"star-outline"} size={15} color="#FFE600" />
)
}
</View>
</ImageBackground>
</TouchableOpacity>
I have console logged item.ids and they are unique 23, 24, and 25.
if You need more code to help. You can ask. Thanks in Advance.
Your code looks right, i don't think the warning you are getting for key is from this specific component, but i would like to add a few things to make your life a little easier.
Never use Index as key. Index will change if your data changes which will eventually make the purpose of keys useless.
Wherever possible use FlatList Component instead of a map since you get a easy keyExtractor Prop and it increases your performance if the list is long
Example : -
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
using the keyExtractor Prop you can pick out and convert a unique value from your data into a key and it automatically adds the key prop into rendered component wrapper.
Related
I am new to React native and implementing FlatList where I am facing some issues with dynamic data. I have a list of total seats and booked seats.
this.state = {
seats: props.route.params.seats,
reservedSeats: props.route.params.Reserved,
date: new Date()
}
Following is the FlatList i have implemented
<FlatList style={styles.flatListArea1}
contentContainerStyle={{margin:0}}
data={this.state.seats}
numColumns={4}
showsHorizontalScrollIndicator={false}
renderItem={({item}) =>
<View style={styles.containerIcons} key={item}>
<TouchableOpacity style={this.state.selectedItem === item ? styles.menuSelected : styles.menuTop } onPress={ () => this.selectSeat(item)}>
<View style={styles.buttons}>
<Text style={styles.HallsText} key={item.key}>{item.Id}</Text>
</View>
</TouchableOpacity>
</View>}
/>
On Click event, I am able to change the color. I appreciate if someone can help to understand, How we can re-render FlatList based on dynamic data presented in reserved state. For example. for today, out of 5 seats 3 are booked. Those should be gray and others should be red.
I appreciate your help on this.
Regards,
You firstly need a method that tells if a seat is available or not.
isReserved = (item) => {
return this.state.reservedSeats.filter(seat => seat.Id ==item.Id).length > 0;
}
Then you change the appearance of your list like this
<FlatList
style={styles.flatListArea1}
contentContainerStyle={{ margin: 0 }}
data={this.state.seats}
numColumns={4}
showsHorizontalScrollIndicator={false}
renderItem={({ item, index }) => (
<View style={[styles.containerIcons, { backgroundColor: isReserved(item) ? "#FAFAFA" : "white" }]} key={index}>
<TouchableOpacity
style={this.state.selectedItem === item ? styles.menuSelected : styles.menuTop}
onPress={() => this.selectSeat(item)}
disabled={isReserved(item)}
>
<View style={styles.buttons}>
<Text
style={[styles.HallsText, { backgroundColor: isReserved(item) ? "#CCC" : "white" }]}
key={item.key}
>
{item.Id}
</Text>
</View>
</TouchableOpacity>
</View>
)}
/>;
Pay attention to the key attribute
I'm new at react native.I have a problem that i've been dealing for a few days.I want to show my data with map.Can you guys show me where i did wrong?
(I think i made mistake in the results inside scrollview)
class Movie extends Component{
state= {
apiurl:'http://www.omdbapi.com/.............................',
s:'',
results: [],
selected:{}
}
searchFunc(s) {
this.setState({s: s})
axios(this.state.apiurl+ "&s="+s).then(response =>
this.setState({results: response.data.Search[0]}));
console.log(this.state.results)
}
render() {
return(
<View style={{flex:1,backgroundColor:'#356292'}}>
<View
style={styles.sectionContainer}>
<View style={styles.section}>
<TextInput style={styles.section2}
onChangeText = {(s) => this.searchFunc({s})}
value={this.state.s}
placeholder="Movies,Series.."
>
</TextInput>
<TouchableOpacity
style={{justifyContent: 'center', alignItems: 'center'}}
onPress={() => this.searchFunc()}>
<Image
source ={require('../img/seach.png')}
style={{width:width*0.05,height:height*0.03}}
>
</Image>
</TouchableOpacity>
</View>
</View>
<ScrollView style={styles.scroll}>
{this.state.results.map(results=>(
<View key={this.state.results.imdbID}
style={styles.scroll2}>
<Image source={{uri: this.state.results}}
style={{width:width*0.3, height:height*0.4}}>
</Image>
<Text style={styles.heading}>
{this.state.results.Title}
</Text>
</View>
))}
</ScrollView>
</View>
);
}
From the comments from your original post, i'm guessing response.data.Search[0] is also an array. If so your results state update is okay. Otherwise you should set the results state to response.data.Search.
Other problem I noticed in your code is in the map function, you are mapping the array with each item refering to the result variable, but in your jsx, you are trying to access the results state variable. It should be
<ScrollView style={styles.scroll}>
{
this.state.results.map(result => (
<View
key={result.imdbID}
style={styles.scroll2}
>
<Image
source={{uri: result.image}} // use correct key from result object
style={{width:width*0.3, height:height*0.4}}
/>
<Text style={styles.heading}>{result.Title}</Text>
</View>
))
}
</ScrollView>
Also in your Textinput onChangeText callback function, you dont have to pass that value as an object. You can change it to
<TextInput
style={styles.section2}
onChangeText={(s) => this.searchFunc(s)}
value={this.state.s}
placeholder="Movies,Series.."
/>
I quess you are supposed to pass Search to the state and not the Search[0], which is the object and not map, the reason map function is not working on it.
axios(this.state.apiurl+ "&s="+s).then(response =>
this.setState({results: response.data.Search}));
console.log(this.state.results)
Coincidently, I have created an app using same movie API, you can find it here: Github Repo, Movie App
I have a Flatlist on react native by which its working perfectly, recently saw an UI which has a custom designed compoenent in between the list, kindly check the below reference image
Check this image, a new design named "Safety+" has been added inside an list, How to do this ?
Need to add custom design like below randomly inside and already rendered flatlist ! How to achieve this , as i couldn't find or understand where to start
Please check the image and help in achieving this:
<FlatList
contentContainerStyle={{ paddingBottom: 50 }}
data={this.state.availableBusList}
keyExtractor={item => item.id}
renderItem={({item}) => {
return(
<TouchableOpacity style={styles.busCardContainer}
onPress={() => {
console.log(item);
//this.props.navigation.navigate('SeatLayout')
}}
<Text>{item.name}</Text>
>)}}
/>
This is my flatlist code
You can return a fragment with your component and a randomly included component. The condition for inclusion is up to you, i.e. complete chance, every 5th element, etc.
<FlatList
contentContainerStyle={{ paddingBottom: 50 }}
data={this.state.availableBusList}
keyExtractor={item => item.id}
renderItem={({item}) => {
return(
<Fragment>
<TouchableOpacity style={styles.busCardContainer}
onPress={() => {
console.log(item);
//this.props.navigation.navigate('SeatLayout')
}}
>
<Text>{item.name}</Text>
</TouchableOpacity>
{Math.random() < 0.5 && ( // example 50% chance to include random component
<RandomComponent>...</RandomComponent>
)}
</Fragment>
)}}
/>
You can render conditionally in your renderItem function: https://reactjs.org/docs/conditional-rendering.html
Additionally, if you want to render your custom component at specific indexes, you can also put index parameter into renderItem. Here is my example:
<FlatList
contentContainerStyle={{ paddingBottom: 50 }}
data={this.state.availableBusList}
keyExtractor={item => item.id}
renderItem={({ item, index }) => {
return index % 5 === 0 ? <CustomComponent /> : <NormalComponent />;
}}
/>
I want to make sure that the selected item is always in the middle of the flatlist. The middle item is selected. The style of the chosen one is different than the others.
<FlatList data={items}
style={styles.listStyle}
ref={(ref) => {
this.flatListRef = ref;
}}
snapToAlignment={'center'}
horizontal
onPress={this.onPressButton}
showsHorizontalScrollIndicator={false}
renderItem={({item, index}) => (
<TouchableWithoutFeedback style={{justifyContent: 'center'}}
onPress={this.onPressButton}>
<View style={{justifyContent: 'center'}}>
<View style={styles.containerView}>
<View
style={[styles.circlesBack, (this.state.selectedId === index) ? styles.circles : styles.circlesBack]}>
{this.state.selectedId === index ?
<FontAwesome size={24} name={item.icon} color="white"/> :
<FontAwesome size={24} name={item.icon} color="#BEBEBE"/>}
</View>
{this.state.selectedId === index ? <Text
style={[styles.itemText, (this.state.selectedId === index) ? styles.itemText : styles.itemTextBack]}>{item.title}</Text> :
<Text> </Text>}
</View>
</View>
</TouchableWithoutFeedback>
)}
keyExtractor={item => item.id}/>
this is what it looks like now
it should look like this
How to do it?
Thank you
You should use like this:
this.flatListRef.scrollToIndex({animated: true, index: 30, viewPosition:0.5})
Valid params keys are:
'animated' (boolean) - Whether the list should do an animation while scrolling. Defaults to true.
'viewPosition' (number) - A value of 0 places the item specified by index at the top, 1 at the bottom, and 0.5 centered in the middle.
Here is someone's code for example, you can instead the line
this.flatListRef.scrollToIndex({animated: true, index: 30,viewPosition:0.5});
of
this.flatListRef.scrollToIndex({animated: true, index: randomIndex});
then you can see it works(at right choose android or ios to run will be better)
docs
But the several begins item seems can't go to middle because of there are no data before. Or you can use react-native-infinite-looping-scroll to let other data at bottom connect back to first. Maybe can achieve this problem.
I have a flat list, which gets its data source as a state. Actually, this data is from firebase, and i have been using redux. So, the data is fetched in the actions, and using callback i get the data to state.
What i want to achieve is, when there is no data found from the api, An empty list message should be show in the view. Actually , i achieved this using "ListEmptyComponent". But whats happening is the screen starts with empty message, and the spinner loads below it, and then if data found the message goes away as well as spinner.
But, what i wanted is, when the view gets rendered the first thing everyone should see is the spinner, and then if data empty spinner hides then empty list message displays.
How to achieve this ?
My Action :
export const fetchOrderHistory = (phone, callback) => {
return (dispatch) => {
dispatch({ type: START_SPINNER_ACTION_FOR_ORDER_HISTORY })
firebase.database().ref('orders/'+phone)
.on('value', snapshot => {
const snapShotValue = snapshot.val();
callback(snapShotValue);
dispatch ({ type: ORDER_HISTORY_FETCHED , payload: snapshot.val()});
dispatch({ type: STOP_SPINNER_ACTION_FRO_ORDER_HISTORY })
});
};
};
My Flat List & spinner:
<FlatList
data={this.state.historyOfOrders}
keyExtractor={item => item.uid}
ListEmptyComponent={this.onListEmpty()}
renderItem={({ item }) => (
<Card
containerStyle={{ borderRadius: 5 }}
>
<View style={styles.topContainerStyle}>
<View>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('ViewOrderScreen', {itemsOfOrder: item}) }
>
<View style={styles.viewOrderContainer}>
<View style={styles.viewOrderTextContainer}>
<Text style={styles.viewOrderTextStyle}>View Order</Text>
</View>
<Icon
name='ios-arrow-forward'
type='ionicon'
color='#ff7675'
/>
</View>
</TouchableOpacity>
</View>
</View>
</View>
</Card>
)}
/>
{this.props.isSpinnerLoading &&
<View style={styles.loading}>
<ActivityIndicator size="large" color="#03A9F4"/>
</View> }
My Call back at componentWillMount which set state:
componentWillMount() {
this.props.fetchOrderHistory((this.props.phone), (snapShotValue)=> {
const userOrderHistory = _.map(snapShotValue, (val,uid) => ({uid, ...val}))
this.setState({ historyOfOrders: userOrderHistory })
});
}
My EmptyList Message:
onListEmpty = () => {
return <View style={{ alignSelf: 'center' }}>
<Text style={{ fontWeight: 'bold', fontSize: 25 }}>No Data</Text>
</View>
}
My State:
state = { historyOfOrders: "" }
I am getting the spinner values from the reducers, using mapStateToProps.
Kindly Guide me, through
you have to do two things for that.
First, show Flatlist only if the loader is stopped. Second, set default value of this.state.historyOfOrders is null and check if this.state.historyOfOrders not null then only show Flatlist.
Here is a code:
{(!this.props.isSpinnerLoading && this.state.historyOfOrders != null) ?
(
<FlatList
data={this.state.historyOfOrders}
keyExtractor={item => item.uid}
ListEmptyComponent={this.onListEmpty()}
renderItem={({ item }) => (
<Card containerStyle={{ borderRadius: 5 }}>
<View style={styles.topContainerStyle}>
<View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('ViewOrderScreen', {itemsOfOrder: item}) }>
<View style={styles.viewOrderContainer}>
<View style={styles.viewOrderTextContainer}>
<Text style={styles.viewOrderTextStyle}>View Order</Text>
</View>
<Icon
name='ios-arrow-forward'
type='ionicon'
color='#ff7675'
/>
</View>
</TouchableOpacity>
</View>
</View>
</Card>
)}
/>
) : null
}
With this condition, even if you want loader above Flatlist you can do that.
The path you should take is rendering only the spinner when the loading flag is set and rendering the list when loading flag is false.
Your render method should be like below
render()
{
if(this.props.isSpinnerLoading)
{
return (<View style={styles.loading}>
<ActivityIndicator size="large" color="#03A9F4"/>
</View> );
}
return (/** Actual List code here **/);
}