I have a FlatList which renders a custom component "Card". I am passing as keyExtractor this function:
const keyExtractor = (item) => item.id;
And my renderItem function looks like this:
const renderItem = ({item, index}) => <Card {...item} />
My question is: should I pass a key to the Card component? I mean, should I do this
const renderItem = ({item, index}) => <Card key={item.id} {...item} />
to avoid my flatlist re-render components and improve the performance of my list? I have seen some people doing this... but I have never done it. If the answer is yes, why we need keyExtractor if each rendered component have a key?
Of course, if I pass a key to my custom component I will do this on its implementation:
return <View key={props.key}>...</View>
Thank you.
You should have a unique property in your data and use it as a key. There is no need to pass the key to your Card component.
If your data is something like this:
data = [
{
id: 'a12f56e5',
name: 'me',
age: 24
}
// ...
];
then you can use id as key.
Also, please take a look at this to improve your FlatList performance
Related
When I search the internet for react-native optimizations / best practices (Especially for FlatLists which are often greedy), I always find the advice not to use the arrow functions <Component onPress={() => ... }.
Example 1 : https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem :
Move out the renderItem function to the outside of render function, so it won't recreate itself each time render function called. (...)
Example 2 : https://blog.codemagic.io/improve-react-native-app-performance/ :
Avoid Arrow Functions : Arrow functions are a common culprit for wasteful re-renders. Don’t use arrow functions as callbacks in your functions to render views (...)
Example 3 : https://medium.com/wix-engineering/dealing-with-performance-issues-in-react-native-b181d0012cfa :
Arrow functions is another usual suspect for wasteful re-renders. Don’t use arrow functions as callbacks (such as click/tap) in your render functions (...)
I understand that it is recommended not to use arrow function (especially in onPress button and FlatList), and to put the components outside of the render if possible.
Good practice example :
const IndexScreen = () => {
const onPress = () => console.log('PRESS, change state, etc...')
return (
<>
<Button
onPress={onPress}
/>
<FlatList
...
renderItem={renderItem}
ListFooterComponent={renderFooter}
/>
</>
)
}
const renderItem = ({ item: data }) => <Item data={data} ... />
const renderFooter = () => <Footer ... />
export default IndexScreen
But, often, I have other properties to integrate into my child components. The arrow function is therefore mandatory:
const IndexScreen = () => {
const otherData = ...(usually it comes from a useContext())...
<FlatList
...
renderItem={({ item: data }) => renderItem(data, otherData)}
/>
}
const renderItem = (data, otherData) => <Item data={data} otherData={otherData} />
export default IndexScreen
In the latter situation, are good practices followed despite the presence of an arrow function ?
In summary, if I remove otherData (for simplicity), are these two situations strictly identical and are good practices followed ?
Situation 1 :
const IndexScreen = () => {
return (
<FlatList
...
renderItem={renderItem}
/>
)
}
const renderItem = ({ item: data }) => <Item data={data} ... />
export default IndexScreen
=== Situation 2 ?
const IndexScreen = () => {
return (
<FlatList
...
renderItem={({ item: data }) => renderItem(data)}
/>
)
}
const renderItem = (data) => <Item data={data} ... />
export default IndexScreen
The answer has nothing to do with arrow functions, but rather understanding reference equality why react might decide to rerender a component.
You can use useCallback to wrap your function. This will cause the reference to renderItem to only update when one of your callback dependencies updates.
const renderItem = useCallback(()=>{
...
},
[otherdata]);
The first situation is ideal, because when your app code runs it will create only one renderItem function. In the second situation, even if it doesn't have otherProps, you're not following the good practice because a new function is created every render in this line
renderItem={({ item: data }) => renderItem(data)}
Hence, making the FlatList rerender every time.
To fix it, you need to memoize the function you pass in the renderItem prop with useCallback
const renderItem = useCallback(({ item: data }) => {
return (<Item data={data} />)
}, []);
...
<FlatList
...
renderItem={renderItem}
/>
so the memoized version will be created only once when the component mounts. And, if you need to inject more data in the render function, you define that data as a dependency of the useCallback hook to create the function only when that data changes, reducing rerenders down the tree.
const renderItem = useCallback(({ item: data }) => {
return (<Item data={data} otherData={otherData} />)
}, [otherData]);
to me as pointed out previously in other answers the issue is mainly due to the fact that if you use arrow functions inside the code, the function get redefined every time.
Also this way of defining a function make this function unnamed so this is harder to track while debugging: on error stack trace you can see the name of a named function directly in the code.
const renderItem = useCallback( function renderItemFunction ({ item: data }) {
return (<Item data={data} otherData={otherData} />)
}, [otherData]);
this way in the stack trace of errors you should see renderItemFunction indication
I have a data array that I am mapping onto a material-ui Typography.
{
array.map((item, id)=> <Typography key={id} value={item.name} />)
}
The code displays the typography with their respective values on the browser as expected, however, I am trying to set the mapped value of Typography into a state like this...
const [data, setData] = useState();
...
{
array.map((item, id) =>
<Typography key={id} value={item.name} {(e)=>setData(e.target.value)} />
)}
This method does not work.
How do I make data to be the value of <Typography/>
If I understand correctly, you're trying to store item.name within local state? How are you receiving the data that you are mapping through?
Are you trying to store each item you're mapping through and represent key-value pairs of an object within local state? What are you trying to do with the state once you have it?
As far as I know, there's is no way to assign values to local state while you are mapping through an array. However, there are multiple ways to assign the data to local state outside of the return.
Also, what is this line doing?
{(e)=>setData(e.target.value)}
It seems you're trying to create a controlled input on a Typography component without an onChange prop, as well as on a component that does not take input.
One more aside, although it is not a big deal -
{
array.map((item, id) =>
<Typography key={id} value={item.name} ..../>
)}
id here is usually referred to as index
Edited for answer
I normally use Redux to manage the global state, but the process should be the same for Context Provider:
const yourComponent = () => {
const [data, setData] = useState();
// This is the array you get from the Context Provider
const yourArray = getArrayFromContextProvider();
useEffect(() => {
// If you want to normalize the array data
if (yourArray) {
const dataObj = {};
yourArray.forEach((item, index) => {
// Make the key unique by using the item id OR you can use index
dataObj[item.id] = item;
// OR you can add the item value as the key
dataObj[item.value] = item;
});
setData(dataObj);
}
// If you just want to store the array
if (yourArray) {
setData(yourArray)
}
}, [yourArray]);
return <div>
{/* ...Your map */}
</div>;
};
export default yourComponent
Im new in ReactNative and I'm trying to take some data from here https://www.dystans.org/route.json?stops=Hamburg|Berlin
When I try console.log results it return full API response. I dont know why in first results.distance works and return distance, but when I'm trying to do it inside FlatList nothing is returned. Sometimes it works when i want to return only item.distance but can't somethnig like <Text>{item.stops[0].nearByCities[0].city}</Text> nowhere in my code also in console. Im getting error:
undefined is not an object (evaluating 'results.stops[0]')
imports...
const NewOrContinueScreen = ({ navigation }) => {
const [searchApi, results, errorMessage] = useDystans();
console.log(results.distance);
return (
<SafeAreaView forceInset={{ top: "always" }}>
<Text h3 style={styles.text}>
Distance: {results.distance}
</Text>
<Spacer />
<FlatList
extraData={true}
data={results}
renderItem={({ item }) => (
<Text>{item.distance}</Text>
// <Text>{item.stops[0].nearByCities[0].city}</Text>
)}
keyExtractor={item => item.distance}
/>
<Spacer />
</SafeAreaView>
);
};
const styles = StyleSheet.create({});
export default NewOrContinueScreen;
And here is my hook code:
import { useEffect, useState } from "react";
import dystans from "../api/dystans";
export default () => {
const [results, setResults] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const searchApi = async () => {
try {
const response = await dystans.get("route.json?stops=Hamburg|Berlin", {});
setResults(response.data);
} catch (err) {
setErrorMessage("Something went wrong with useDystans");
}
};
useEffect(() => {
searchApi();
}, []);
return [searchApi, results, errorMessage];
};
As the name implies, FlatList is designed to render a list. Your API endpoint returns a JSON Object, not an Array, so there's nothing for the FlatList to iterate. If you want to show all the stops in the list, try passing in the stops list directly.
<FlatList
data={results.stops}
renderItem={({ item }) => (<Text>{item.nearByCities[0].city}</Text>)}
/>
Some side notes: (1) The extraData parameter is used to indicate if the list should re-render when a variable other than data changes. I don't think you need it here at all, but even if you did, passing in true wouldn't have any effect, you need to pass it the name(s) of the variable(s). (2) The keyExtractor parameter is used to key the rendered items from a field inside of them. The stop objects from the API don't have a member called distance so what you had there won't work. From my quick look at the API response, I didn't see any unique IDs for the stops, so you're probably better off letting React key them from the index automatically.
I wanted to know if KeyExtrator contributes to maintain the unique list ?
if yes then its not helping me at all.
I am trying to display elements on flat list when a new Item comes in, the list just addes it without checking for uniqueness of the list
Main goal is to make my list unique, I had tried ListView It was working
due to logic implemented using rowHasChanged method.trying to see if there is anything for FlatList.
constructor(props) {
super(props);
this.state = {
TESTDATA: [{
BarCode: '1',
StopAddress: 'Label 1'
}]
};
};
onReceivedMessage(messages) {
var jsondata = JSON.parse(messages)
var dataTest = this.state.TESTDATA;
var Data = addressToDataMap.get(dataTest.BarCodes);
dataTest.push(jsondata);
this.setState({
TESTDATA: dataTest
});
}
<FlatList
extraData={this.state}
data={this.state.TESTDATA}
keyExtractor={(item, index) => item.BarCode.toString()}
renderItem={({ item}) => (
<Text style={styles.baseText}>
Stop Address: {item.StopAddress}{'\n'}
Barcodes: {item.BarCode}
</Text>
)}
/>
I wanted to know if KeyExtrator contributes to maintain the unique list ? NO is the Answer
Explanation : keyExtractor tells the list to use the ids for the react keys instead of the default key property. Keys help React identify which items have changed, are added, or are removed. Key also used for caching and as the react key to tracking item re-ordering. These unique keys are what allow the VirtualizedList (which is what FlatList is built on) to track items and are really important in terms of efficiency. The keyExtractor falls back to using the index by default. For example
<FlatList
style={{}}
data={this.state.FeedDataCollection}
keyExtractor={(item, index) => item.key}
renderItem={(rowData) =>this.RenderFeedCard(rowData)}
/>
Here item.key is is key.So if you want display elements based on the uniqueness, you have write a logic for that. keyExtractor cant do that. Learn more about React Keys here https://reactjs.org/docs/lists-and-keys.html and KeyExtractor here https://facebook.github.io/react-native/docs/flatlist#keyextractor
I have a list just simple text that rendering into flatlist on react native but I am experiencing very very slow performance which makes app unusable.
How can I solve this? My code is:
<FlatList
data={[{key: 'a'}, {key: 'b'} ... +400]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
Here is my suggestions:
A. Avoid anonymous arrow function on renderItem props.
Move out the renderItem function to the outside of render function, so it won't recreate itself each time render function called.
B. Try add initialNumToRender prop on your FlatList
It will define how many items will be rendered for the first time, it could save some resources with lot of data.
C. Define the key prop on your Item Component
Simply it will avoid re-render on dynamically added/removed items with defined key on each item. Make sure it is unique, don't use index as the key! You can also using keyExtractor as an alternative.
D. Optional optimization
Try use getItemLayout to skip measurement of dynamic content. Also there is some prop called maxToRenderPerBatch, windowSize that you can use to limit how many items you will rendered. Refer to the official doc to VirtualizedList or FlatList.
E. Talk is Cheap, show me the code!
// render item function, outside from class's `render()`
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
// we set the height of item is fixed
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'a' }, { key: 'b'}, ...+400];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Try out this listview https://github.com/Flipkart/ReactEssentials, it renders far fewer items than FlatList and then recycles them. Should be much faster.
npm install --save recyclerlistview
check this link
https://github.com/filipemerker/flatlist-performance-tips
Example
FlatList
containerContentStyle={styles.container}
data={countries}
renderItem={({ item }) => (
<View style={styles.results}>
<Results
{...this.props}
country={item}
handleUpdate={this.handleUpdate}
pendingCountry={pendingCountry}
/>
</View>
)}
keyExtractor={item => item.alpha2code}
ListHeaderComponent={() => this.renderHeader()}
// Performance settings
removeClippedSubviews={true} // Unmount components when outside of window
initialNumToRender={2} // Reduce initial render amount
maxToRenderPerBatch={1} // Reduce number in each render batch
updateCellsBatchingPeriod={100} // Increase time between renders
windowSize={7} // Reduce the window size
/>
One of the simple ways to optimize your flatlist is by using React.memo. In technical words, it basically does a shallow comparison of your data and check whether they needs to be re-rendered or not.
Make a file such as ListComponent.js and add the renderItem JSX to it, and and it to the renderItem.
// ListComponent.js
import React, { memo } from "react";
import { StyleSheet, Text, View } from "react-native";
const ListComponent = ({ item }) => {
return <View ></View>
};
export default memo(ListComponent);
Here is your FlatList
<FlatList
data={data}
removeClippedSubviews={true}
maxToRenderPerBatch={8}
windowSize={11}
initialNumToRender={8}
keyExtractor={keyExtractor}
renderItem={({ item }) => (
<ListComponent item={item} />
)}
/>
Another optimization would be to provide a key using keyExtractor prop. It's very important.
I used 'react-native-optimized-flatlist'
and my problem was solved, the only thing to be careful about is that when you use this package, it removes keyExtractor and extraData
You can use react-native-optimized-flatlist. It is the optimized version of Flatlist.
1) Add this package by :
npm i -S react-native-optimized-flatlist
OR
yarn add react-native-optimized-flatlist
2) Replace <FlatList/> by <OptimizedFlatlist/>