So I have a FlatList that gets fed an array of items. When I scroll to the bottom, I append more items to the end of that array and show to the user.
The issue is every single is item is rendered when we add to our item array, and sometimes even rendered twice.
Code is simplified here. /r/reactnative was unable to answer this question.
constructor(props) {
super(props);
this.state = {itemsTest: ['A', 'A', 'A', 'A']}
}
render() {
// Key is fine, since none of my items are changing indexes. I am just adding new items.
return (
<FlatList
keyExtractor={(item,index) => index}
scroll
data={this.state.itemsTest}
renderItem={({item, index}) => <View style={{width: windowWidth}}><Text>{item}</Text></View>
onEndReached={() => this.nextItemsTest()}
onEndReachedThreshold={0.2}
</FlatList>
)
}
nextItemsTest() {
// From suggestions below, just add an element to this array.
console.log('nextItemsTest');
const x = ['A'];
// Have worked with many variations of setting state here. I don't think this is the issue.
this.setState((prevState) => ({itemsTest: [...prevState.itemsTest, ...x],}));}
Here's the output. Every single item is re-rendered (twice even) every time my state is set.
I just want to re-render the items that haven't changed. Thank you.
Instead of Using View directly in your flatlist render you can create another component which is a pure component. so it will only re-renders when its data changes . e.g For your case it re-renders each item only once.
here is the solution
1st create a pure component like this
class SmartView extends PureComponent {
render() {
const {item, index} = this.props;
return (
<View style={{height: 300}}>
{console.log('rendering', index)}
<Text>{item}</Text>
</View>
);
}
}
and then replace View with SmartView in your flatlist like this
<FlatList
keyExtractor={(item, index) => index.toString()}
data={this.state.itemsTest}
renderItem={({item, index}) => <SmartView item=
{item} index={index} />}
onEndReached={() => this.nextItemsTest()}
onEndReachedThreshold={0.2}
/>
Related
Right now I have a flatlist that contains a bunch of firebase objects (books), when one of the book are clicked, I want to return a page with more data about that specific book. Right now each book it an object, where title is one of the values, this is what is shown on the flatlist. I want to be able to show all of the other object attributes when the new detailed page is opened. If there is a better way of doing this, let me know but this is the logic that I was trying to go with.
(this is in the flatlist)
renderItem={({ item }) => (
<TouchableOpacity onPress={bookOnPressHandler} activeOpacity={0.9} style={styles.flatListStyle}>
<View>
<Text>{item.title}</Text>
</View>
</TouchableOpacity>
)}
/>
then the handler:
const bookOnPressHandler = (item) => {
//this holds the title of the book
title = item.title
console.log(title)
navigation.navigate('booknotes')
}
I obviously need to pass item into the function, what it is right now wont work. How would I get access to "Item" in the function? once I set the item = to something, I can use it on the new page.
I feel like there is a better method than this and that this might even not work? I know this is a common thing to do in apps, all help is really appreciated. Also sorry if its obvious, Im pretty new to this language and framework!
Since you already have access to the required object that is to be passed to the next screen, it's very easy, you can pass it along with the navigation object as a route param.
It's always good to isolate the prop functions to separate functions to avoid unnecessary re-render of the component.
Here is an example.
<FlatList
ref={ref}
contentContainerStyle={styles.contentContainer}
scrollEventThrottle={16}
numColumns={2}
data={exploreData}
initialNumToRender={2}
renderItem={renderItem}
keyExtractor={(item: any) => item.id.toString()}
})}
/>
Here is the renderItem function:
const renderItem = ({ item }) => {
const handleOnPress = () => navigation.navigate("Profile", { item });
return (
<TouchableWithoutFeedback
onPress={handleOnPress}
>
<ImageBackground
source={{ uri: img }}
style={styles.image}
imageStyle={styles.background}
/>
</TouchableWithoutFeedback>
);
};
//Profile Screen
const Profile = ({ navigation, route }) => {
const { item } = route.params;
console.log(item);
};
I am still having trouble understanding ref's in React Native (and React in general). I am using functional component. I have a FlatList that has many items. How do I create a reference for a thing within an item like a Text or View component?
<FlatList
data={data}
renderItem={({ item }} => {
<View>
... lots of other stuff here
<TouchableOpacity onPress={() => _editITem(item.id)}>
<Text ref={(a) => 'text' + item.id = a}>EDIT</Text>
</TouchableOpacity>
</View>
}
/>
Then in _editItem I want to reference the Text component so that I can change its text from 'EDIT' to 'EDITING', or even change its style, or whatever.
_editPost = id => {
console.log(text + id)
}
I have tried...
FeedComponent = () => {
let editPosts = {}
<FlatList
data={data}
renderItem={({ item }} => {
<View>
... lots of other stuff here
<TouchableOpacity onPress={() => _editITem(item.id)}>
<Text ref={(a) => editPosts[item.id] = a}>EDIT</Text>
</TouchableOpacity>
</View>
}
/>
...and a few other things, but I think I might be way off so I could use some guidance.
Typically you don't use refs in react to update content like text. Content should be rendered based on the current props and state of your component.
In the case you describe you'll probably want to set some state in the parent component that then impacts the rendering of the item.
As a sidenote refs are used if you need to trigger a method on a child component like calling focus on a TextInput for example but not for imperatively updating component content.
In your case you'll want to update some state representing the current active item. Something like:
import React, {useState} from 'react';
FeedComponent = () => {
const [activeItem, setActiveItem] = useState(null);
<FlatList
data={data}
renderItem={({ item }} => {
return (
<View>
... lots of other stuff here
<TouchableOpacity onPress={() => setActiveItem(item.id)}>
{activeItem === item.id
? <Text>EDITING</Text>
: <Text>EDIT</Text>
}
</TouchableOpacity>
</View>
);
}
extraData={activeItem}
/>
I am wondering which is the right way to assign onPress function for the FlatList item in React-Native. My mentor has explained for me that maybe I missed the knowledge of "delegate/closure/block" definition in OOP, I had read it but until now I still cannot figure it out by myself. This is the detail:
I have a screen named Menu – this menu has a <FlatList> which contains multiple <MenuItem>, each <MenuItem> has a toggle button to "add item to cart" or "remove item for cart" (click to add – click again to remove). I seperate <MenuItem> to another file so my current folder-tree looks like this:
__Menu
| |_MenuItem
| |__index.js
|
|__index.js
And this is my mentor's approach:
He defined a function named _onToggleCart in Menu/index.js like this:
_onToggleCart = (selected) => {
if(selected == false){
this.props.addItemToCart()
}else{
this.props.removeItemFromCart()
}
}
<FlatList
data={data}
initialNumToRender={6}
extraData={this.state.data}
keyExtractor={(item) => item.id}
renderItem={({ item, index }) => <MenuItem item={item} isFinalItem={index == data.length - 1} navigation={navigation} onPress={this._onToggleCart} />}
/>
Menu/MenuItem/index.js looks like this:
...
const [selected, setSelected] = useState(false)
const { id, imgURL, name, desciption, total } = item
_onChangeCart = () => {
setSelected(!selected)
onPress(selected)
}
...
<TouchableOpacity>
...
<TouchableWithoutFeedback onPress={this._onChangeCart}>
...
</TouchableWithoutFeedback>
...
</TouchableOpacity>
And this is my approach:
In Menu/index.js I do not assign any function to <MenuItem>, so I don't have a function _onToggleCart and also don't have onPress props. It looks like this:
<FlatList
data={data}
initialNumToRender={6}
extraData={this.state.data}
keyExtractor={(item) => item.id}
renderItem={({ item, index }) => <MenuItem item={item} isFinalItem={index == data.length - 1} navigation={navigation} />}
/>
In Menu/MenuItem/index.js to check the condition in _onChageCart like this:
...
const [selected, setSelected] = useState(false)
const { id, imgURL, name, desciption, total } = item
_onChangeCart = () => {
if(selected == false){
this.props.addItemToCart()
}else{
this.props.removeItemFromCart()
}
}
...
<TouchableOpacity>
...
<TouchableWithoutFeedback onPress={this._onChangeCart}>
...
</TouchableWithoutFeedback>
...
</TouchableOpacity>
Can anyone help me what is different between them and which is better?
The main difference from you component and the one of your mentor is that he passes a onPress prop to MenuItem. What difference does this makes?
Well, in your example, if you need to add more action on the MenuItem press, how do you do? You can't do it, but the way your mentor did, you can add more functionality (call one more function) to the MenuItem because he call props.onPress.
You mentor did a more flexible component, wich maybe can grow easier, your component is more fixed.
Wich one is better? Now that is up to you and the structure of your project.
Here I am trying to display an array called "posts" in a FlatList.
render() {
console.log(this.props.posts);
return (
<View style={this.styles.container}>
<FlatList
data={this.props.posts}
renderItem={(item) => <Text> {item.name} </Text>}
/>
</View>
);
}
As seen in this console log, the posts array is correctly populated.
But the above code doesn't display any data in the FlatList.
However, in renderItem, if I add an extra "item" property it works.
renderItem={(item) => <Text> {item.item.name} </Text>}
What is the reason for this behavior.
Input of ReactNative's FlatList is not item, but an object containing 3 parameters: item for actual data, index for index and separators object to customize your item component. What you did is naming that object item, and get actual item from the object.
To avoid confusion, consider using ES6 shorthand:
renderItem={({ item, index }) => <Text> {item.name} </Text>}
This is a common behavior. You can get required behavior by doing object destructuring as:
<FlatList
data={this.props.posts}
renderItem={({item}) => <Text> {item.name} </Text>}
/>
If you are rendering complex component, then you might want to do like this for sake of readability of the code however.
<FlatList
data={this.props.posts}
renderItem={this.renderItem} />
renderItem = ({item}) => {
return (
<Text>{item.name}</Text>
)
}
Might wanna look into your question here though.
ReactNative Flatlist - RenderItem not working
Im using a flat list on 2 different screens.
On the EventListScreen:
this is the main screen and should display all events.
and on the 2nd page UserProfile.js this page should only display that users events.
in both flat lists I'm using a pure component stored in a seperate class, to where the flat lists are i.e
My Question is, I want to display an "Edit" button on the Event.js child component only if the User is on the
UserProfileScreen.js
I have looked up a lot of example but cant really find any that show how to do it
with a child pure component like I'm doing.
Any Help would be greatly appreciated! Thank you
EventListScreen.js
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
{...item}
/>}
/>
UserProfileScreen.js
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
openEditEvent={() => this.openEditEvent(item)}
{...item}
/>}
/>
Event.js
export default class Event extends Component {
render() {
return (
<Card>
<CardSection>
<Text>{this.props.eventName}</Text>
//I want this button to be displayed only if user is viewing
//from the UserProfile.js
<Button onPress={() =>this.props.openEditEvent()}>
{this.props.displayButton}
</Button>
</CardSection>
<TouchableOpacity
onPress={() => this.props.openEventDetail()}
>
}
You don't need additional properties.
We can assume that the "Edit" button should be available when openEditEvent prop is defined.
Condition in event (using convertion to bool, false for undefined):
<CardSection>
<Text>{this.props.eventName}</Text>
{!!this.props.openEditEvent &&
<Button onPress={() =>this.props.openEditEvent()}>
{this.props.displayButton}
</Button>
}
</CardSection>
Use propTypes to define openEditEvent prop as a function, optional (not required).
If I understand your problem correctly an option to solve this problem would be to pass a boolean "showable prop" to show the edit button only when required:
EventListScreen.js (Stays the same, we don't show the edit button here)
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
{...item}
/>}
/>
UserProfileScreen.js (we add the shouldShowEditButton prop to event in order to show the button)
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
openEditEvent={() => this.openEditEvent(item)}
shouldShowEditButton
{...item}
/>}
/>
Event.js (We add some propTypes and defaultProps to handle the new prop, it won't show the edit button if not specified)
export default class Event extends Component {
render() {
return (
<Card>
<CardSection>
<Text>{this.props.eventName}</Text>
//I want this button to be displayed only if user is viewing
//from the UserProfile.js
{this.props.shouldShowEditButton && <Button onPress={() =>this.props.openEditEvent()}>
{this.props.displayButton}
</Button>}
</CardSection>
<TouchableOpacity
onPress={() => this.props.openEventDetail()}
>
...
...
);
...
}
}
// We add some default propTypes and definitions
Event.propTypes = {
shouldShowEditButton: PropTypes.bool
};
Event.defaultProps = {
shouldShowEditButton: false
};
In this way you're only showing the edit button for the components that have the prop shouldShowEditButton defined, and because its default value is defined as false, the components that don't have the property will behave in the same way they were before.