i'm currently trying to implement a horizontal FlatList. I'm aware that i can render list of items pretty easly inside the renderItem by just looping it through... but can i actually pass a custom component inside ?
This is my array filled with custom components i created:
const arrayOfCustomComponents = [<Home/>,<News/>,<History/>,<Stats/>,<Settings />];
Given this array, can i pass each index inside renderItem to be rendered ?
<Animated.FlatList
data={data}
keyExtractor={item => item.key}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
bounces={false}
renderItem={({item}) =>{
return <View>
{arrayOfCustomComponents[item.key]}
</View>
}}
/>
You cannot access an array elements like you've done in your example. The keys of an array are 0, 1, 2, ....
What you can do instead is to store your components in an object like that:
const DATA = [
{
id: 'comp1',
description: 'This is component 1',
},
{
id: 'comp2',
description: 'This is component 2',
}
];
const Comp1 = () => {
return (
<View><Text>This is component 1</Text></View>
)
}
const Comp2 = () => {
return (
<View><Text>This is component 2</Text></View>
)
}
const mapOfComponents = {
comp1: <Comp1 />,
comp2: <Comp2 />
};
function App() {
return (
<View>
<FlatList
data={DATA}
renderItem={({ item }) => {
return (
mapOfComponents[item.id]
)
}
}
keyExtractor={item => item.id}
/>
</View>
);
}
Related
New to react native. I have a simple component that takes in a list of ingredients and returns the items in a flatList of text. For some reason I can't get the data to render. Am I doing something wrong?
My ingredients looks like this:
const ingredients = [chicken, butter, oil]
const DisplayRec = ({ ingredients }) => {
return (
<View style={styles.container}>
<Text>Your Recipes</Text>
<FlatList
//keyExtractor={(recName) => recName}
data={ingredients}
renderItem={({ recName }) => (
<Text>{recName}</Text>
)}
/>
</View>
);
};
You are using it in incorrect manner
please try
<FlatList
//keyExtractor={(recName) => recName}
data={ingredients}
renderItem={({item, index}) => (
<Text>{item}</Text>
)}
/>
also please go throught the documentation of FlatList
https://reactnative.dev/docs/flatlist#required-renderitem
First you have to go through with the official documentation of
React-native .
flatlist documentation
you can just simply pass ingredients data to flatlist and render its
function
For live editing expo link
const ingredients = [
{
id: 'bd7',
title: 'chicken',
},
{
id: '3ac',
title: 'butter',
},
{
id: '5869',
title: 'oil',
},
];
export default function App() {
const renderItem = ({ item }) => (
<Text>{item.title}</Text>
);
return (
<View >
<FlatList
data={ingredients}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
}
you need to use return in render item
<FlatList
//keyExtractor={(recName) => recName}
data={ingredients}
renderItem={({ recName }) => {
return (
<Text>{recName}</Text>
)}}
/>
Hope it's working fine for you
Please! Can somebody explain me whats wrong with following code. I'm trying to pass ListItem
export const ListItem: ListRenderItem<IUser> = ({item}) => {
return (
<RNEListItem onPress={() => {}}>
<Avatar source={{uri: item.picture.thumbnail}} rounded size="medium" />
<RNEListItem.Content>
<RNEListItem.Title>{`${item.name.first} ${item.name.last}`}</RNEListItem.Title>
<RNEListItem.Subtitle>{item.email}</RNEListItem.Subtitle>
</RNEListItem.Content>
<RNEListItem.Chevron size={30} />
</RNEListItem>
);
};
to renderItem prop in FlatList
return (
<FlatList
data={users}
renderItem={ListItem}
ItemSeparatorComponent={ListItemSeparator}
keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false}
ListFooterComponent={ListLoader}
onEndReached={handleMore}
onEndReachedThreshold={0.1}
onRefresh={handleRefresh}
refreshing={isRefreshing}
/>
);
everything fine. But when I'm trying to use hooks
export const ListItem: ListRenderItem<IUser> = ({item}) => {
const {navigate} = useNavigation<RootStackParamList>();
const handlePress = useCallback(() => {
console.log(item.login.uuid);
navigate(ERootStackScreens.USER_SCREEN, {id: item.login.uuid});
}, []);
return (
<RNEListItem onPress={() => {}}>
<Avatar source={{uri: item.picture.thumbnail}} rounded size="medium" />
<RNEListItem.Content>
<RNEListItem.Title>{`${item.name.first} ${item.name.last}`}</RNEListItem.Title>
<RNEListItem.Subtitle>{item.email}</RNEListItem.Subtitle>
</RNEListItem.Content>
<RNEListItem.Chevron size={30} />
</RNEListItem>
);
};
RN return's
Hooks can only be called inside the body of a function component.
but when i change renderItem this way
return (
<FlatList
data={users}
renderItem={()=>ListItem}
ItemSeparatorComponent={ListItemSeparator}
keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false}
ListFooterComponent={ListLoader}
onEndReached={handleMore}
onEndReachedThreshold={0.1}
onRefresh={handleRefresh}
refreshing={isRefreshing}
/>
);
Everything becomes fine. But it looks like types in docs are incorrect. Cuz according to them first example should work without problems
renderItem: ListRenderItem<ItemT> | null | undefined;
export type ListRenderItem<ItemT> = (info: ListRenderItemInfo<ItemT>) => React.ReactElement | null;
You need to pass the props in the functional component:
renderItem={(props)=><ListItem {...props} />}
I need to set the state to detect which item in the current viewport is visible. for this purpose, I write below code:
const [inViewPort, setInViewPort] = useState(0);
const viewabilityConfig = {
viewAreaCoveragePercentThreshold: 30,
};
const onViewableItemsChanged = ({viewableItems, changed}) => {
if (changed && changed.length > 0) {
setInViewPort(changed[0].index);
}
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={myData}
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
keyExtractor={(_, index) => index.toString()}
horizontal={true}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
/>
</SafeAreaView>
);
The onViewableItemsChanged event callback triggers correctly but after I call setInViewPort so the component updated and it rerendered the below error encounter:
Invariant Violation: Changing onViewableItemsChanged on the fly is not supported
I was looking for the same answer and there is no clear example anywhere. This is how I solved it.
const [inViewPort, setInViewPort] = useState(0)
const viewabilityConfig = useRef({
itemVisiblePercentThreshold: 50,
waitForInteraction: true,
minimumViewTime: 5,
})
const onViewableItemsChanged = React.useRef(({ viewableItems, changed }) => {
if (changed && changed.length > 0) {
setInViewPort(changed[0].index);
}
})
return (
<SafeAreaView style={styles.container}>
<FlatList
data={myData}
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
keyExtractor={(_, index) => index.toString()}
horizontal={true}
onViewableItemsChanged={onViewableItemsChanged.current}
viewabilityConfig={viewabilityConfig.current}
/>
</SafeAreaView>
)
I am rendering a list in React Native which currently has about 900 list items. I'm mapping through the list and rendering one component for each item. It currently takes about 3 seconds for React to do this which is unacceptable - I would like it to be near instant. Props are passed to the list item component from the redux store and the list items are nested inside the React Native ScrollView Component.
How can I can I increase the performance of rendering these components so there is not such a huge lag?
Here is my Contacts component:
class Contacts extends Component {
renderRegisteredUsers = (contacts) => {
return contacts.items.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.user.address_book_name}
surname={''}
phoneNumber={contact.user.phone}
isRegisteredUser={true}
ccId={contact.user.id}
/>
))
}
renderContacts = (contacts) => {
if (contacts) {
return contacts.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.firstName}
surname={contact.surname}
phoneNumber={contact.phoneNumber}
isRegisteredUser={false}
/>
))
} else {
return (
<>
<Loader />
</>
)
}
}
render() {
return (
<>
<ScrollView>
<Text style={{ fontSize: 22 }}>
Your Contacts Using Fleeting
</Text>
{this.renderRegisteredUsers(this.props.user.registeredContacts)}
<Text style={{ fontSize: 22 }}>
Phone Contacts
</Text>
{this.renderContacts(this.props.user.parsedContacts)}
</ScrollView>
</>
)
}
}
const mapStateToProps = (state) => {
const { user } = state;
return { user }
};
export default connect(mapStateToProps)(Contacts);
And my ContactListItem component:
class ContactListItem extends Component {
constructor(props) {
super(props)
}
handleOnClick = () => {
this.props.calleeId(this.props.ccId)
Actions.TimeInput();
}
render() {
return (
<View style={{ margin: 20, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Text>
{this.props.firstName + ' ' + this.props.surname + ' ' + this.props.phoneNumber}
</Text>
<Icon name="adduser" size={40} color="green" style={{ alignSelf: "flex-end" }} onPress={this.handleOnClick} />
</View>
)
}
}
const mapDispatchToProps = {
calleeId,
};
export default connect(null, mapDispatchToProps)(ContactListItem);
Thanks in advance.
1- You can use PureComponent instead of Component. PureComponent re-renders only when its props change and not re-rendering on each parent re-render. More Information: https://reactjs.org/docs/react-api.html#reactpurecomponent
2- Use unique keys when you're mapping on your items.
3- You can use FlatList instead of ScrollView. It supports Scroll Loading. You can set a number of initial numbers and render the others on scroll. More Information: https://facebook.github.io/react-native/docs/flatlist
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'first' }, { key: 'second'}, ...+1000];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Look into using pagination instead- https://www.npmjs.com/package/react-paginate, https://www.npmjs.com/package/react-native-pagination. If your contacts are coming from an api request, you will have to look into paginating the requests too.
import React, { Component } from 'react';
import {AppRegistry,StyleSheet,View,FlatList,} from 'react-native';
import ContactItem from './Pages/widgets/ContactItem'; // https://github.com/garrettmac/react-native-pagination/blob/master/ReactNativePaginationExample/Pages/widgets/ContactItem.js
import faker from 'faker';//assuming you have this.
import _ from 'lodash';
import Pagination,{Icon,Dot} from 'react-native-pagination';//{Icon,Dot} also available
export default class ReactNativePaginationExample extends Component {
constructor(props){
super(props);
this.state = {
items: this.props.contacts,
};
}
//create each list item
_renderItem = ({item}) => {
return (<ContactItem index={item.id}
onPressItem={this.onPressItem.bind(this)}
name={item.name}
avatar={item.avatar}
description={item.email}
tag={item.group}
createTagColor
/>)
};
//pressed an item
onPressItem = (item) => console.log("onPressItem:item ",item);
_keyExtractor = (item, index) => item.id;
onViewableItemsChanged = ({ viewableItems, changed }) =>this.setState({viewableItems})
render() {
return (
<View style={[s.container]}>
<FlatList
data={this.state.items}
ref={r=>this.refs=r}//create refrence point to enable scrolling
keyExtractor={this._keyExtractor}//map your keys to whatever unique ids the have (mine is a "id" prop)
renderItem={this._renderItem}//render each item
onViewableItemsChanged={this.onViewableItemsChanged.bind(this)}//need this
/>
<Pagination
// dotThemeLight //<--use with backgroundColor:"grey"
listRef={this.refs}//to allow React Native Pagination to scroll to item when clicked (so add "ref={r=>this.refs=r}" to your list)
paginationVisibleItems={this.state.viewableItems}//needs to track what the user sees
paginationItems={this.state.items}//pass the same list as data
paginationItemPadSize={3} //num of items to pad above and below your visable items
/>
</View>
)
}
};
const s = StyleSheet.create({
container: {
flex: 1,
// backgroundColor:"grey",//<-- use with "dotThemeLight"
},
});
AppRegistry.registerComponent('ReactNativePaginationExample', () => App);
I have subdivided my components and I want to change state of text using deleteName function from child component. However I have used onPress={this.props.delete(i)} in my child component which is not working. The error that occurs for me is:
undefined variable "I"
Here is my code:
App.js
export default class App extends Component {
state = {
placeName: '',
text: [],
}
changeName = (value) => {
this.setState({
placeName: value
})
}
deleteName = (index) => {
this.setState(prevState => {
return {
text: prevState.text.filter((place, i) => {
return i!== index
})
}
}
}
addText = () => {
if (this.state.placeName.trim === "") {
return;
} else {
this.setState(prevState => {
return {
text: prevState.text.concat(prevState.placeName)
};
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Input changeName={this.changeName}
value={this.state.placeName} />
<Button title="Send" style={styles.inputButton}
onPress={this.addText} />
</View>
<ListItems text={this.state.text} delete={this.deleteName}/>
{/* <View style={styles.listContainer}>{Display}</View> */}
</View>
);
}
}
and child component ListItems.js
const ListItems = (props) => (
<View style={styles.listitems}>
<Text>{this.props.text.map((placeOutput, i) => {
return (
<TouchableWithoutFeedback
key={i}
onPress={this.props.delete(i)}>
onPress={this.props.delete}
<ListItems placeName={placeOutput}/>
</TouchableWithoutFeedback>
)
})}
</Text>
</View>
);
You need to bind the index value at the point of passing the props to the child.
delete = index => ev => {
// Delete logic here
}
And in the render function, you can pass it as
items.map((item, index) => {
<ChildComponent key={index} delete={this.delete(index)} />
})
In your child component, you can use this prop as
<button onClick={this.props.delete}>Click me</button>
I have created a Sandbox link for your reference
Instead of onPress={this.props.delete(i)}, use onPress={() => this.props.delete(i)}
In order to have the cleaner code, you can use a renderContent and map with }, this);like below. Also you need to use: ()=>this.props.delete(i) instead of this.props.delete(i) for your onPress.
renderContent=(that)=>{
return props.text.map((placeOutput ,i) => {
return (
<TouchableWithoutFeedback key={i} onPress={()=>this.props.delete(i)}>
onPress={this.props.delete}
</TouchableWithoutFeedback>
);
}, this);
}
}
Then inside your render in JSX use the following code to call it:
{this.renderContent(this)}
Done! I hope I could help :)