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]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With