Difficulty understanding the value of removing arrow functions () => - javascript

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

Related

React - useCallback vs useMemo for JSX elements

I have implemented this component:
function CardList({
data = [],
isLoading = false,
ListHeaderComponent,
ListEmptyComponent,
...props
}) {
const keyExtractor = useCallback(({ id }) => id, []);
const renderItem = useCallback(
({ item, index }) => (
<Card
data={item}
onLayout={(event) => {
itemHeights.current[index] = event.nativeEvent.layout.height;
}}
/>
),
[]
);
const renderFooter = useCallback(() => {
if (!isLoading) return null;
return (
<View style={globalStyles.listFooter}>
<Loading />
</View>
);
}, [isLoading]);
return (
<FlatList
{...props}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={renderFooter()}
ListEmptyComponent={ListEmptyComponent}
/>
);
}
As my CardList component is heavy, I have tried to optimize it following these tips.
But, I think that instead of using useCallback for renderFooter, I should use useMemo, in order to memoize the resulted JSX and not the method:
const ListFooterComponent = useMemo(() => {
if (!isLoading) return null;
return (
<View style={globalStyles.listFooter}>
<Loading />
</View>
);
}, [isLoading]);
Am I correct?
If you want to avoid expensive calculations use useMemo. useCallback is for memoizing a callback/function.
Official docs do have an example of using useMemo with JSX for avoiding re render:
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
Personal observation:
But to be more detailed, it seems it is not that useMemo here magically prevents re render, but the fact that you render same reference on the same spot in component hierarchy, makes react skip re render.
Here:
let Child = (props) => {
console.log('rendered', props.name);
return <div>Child</div>;
};
export default function Parent() {
let [a, setA] = React.useState(0);
let [b, setB] = React.useState(0);
// Only re-rendered if `a` changes:
const child1 = React.useMemo(() => <Child a={a} name="a" />, [a]);
// Only re-rendered if `b` changes:
const child2 = React.useMemo(() => <Child b={b} name="b" />, [b]);
return (
<div
onClick={() => {
setA(a + 1);
}}
>
{a % 2 == 0 ? child2 : child1}
</div>
);
}
If you keep clicking the div you can see it still prints "rendered b" even though we never changed the b prop. This is because react is receiving a different reference each time inside the div, and doesn't optimize the re render - like it does if you had simply rendered only child2 without that condition and child1.
Note: The fact that react skips rendering when it receives same element reference in the same spot is known, so apparently useMemo technique works because of that when it comes to rendering optimization.
"Should" is a matter of opinion, but you certainly can. React elements are fully reusable. It's not likely to make any real difference to your component, though, since creating the elements is fast and renderFooter is just used immediately in a component that's already running (unlike keyExtractor and renderItem, which you're passing to FlatList, so you want to make them stable where possible so FlatList can optimize its re-rendering). But you certainly can do that.
useMemo is perfectly sensible here as it memoizes the result (the JSX). As you say, useCallback memoizes the function not the result.

React Native FlatList key

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

How can I acess value from textfield in another module in reactJS

I want to have access to the value of a textField in another module in reactjs. The module that wants to have access does not import the whole textfield but only needs access to the value. What is the best way to access the value variable in another module in reactjs?
Here is my functional textField component:
export default function textField(props) {
const [value, setValue] = React.useState("");
const handleChange = (event) => {
setValue(value);
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
You can send onTextFieldChange function as props whenever textField's value changes you can pass a value to onTextFieldChange function and you can use it in the parent component.
There is an alternate way, Redux
You should try to use redux for the shared state between components which are either not related directly(i.e. sibling components or have a lengthy hierarchy). For small applications, redux is overkilled so should be avoided.
The most likely option that comes to mind here is the concept of lifting state, in which the nearest ancestor component has some means by which it also keeps track of the state, and the passes it into the sibling that needs to track it. You could make this an optional feature of your module by allowing a onChangeCallback prop that is called on each change; this prop could then be passed a setSharedState hook that would set the state on the ancestor:
const ParentComponent = () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
And you update your module to something like:
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
This is just a rough example. Other options for passing around state freely would be using Redux or the Context API, but the former might be overkill for this one case and the latter probably not a great fit for a specific, single-use datapoint.
there are may option but the proper option in pass as props
const modle1= () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
or you can use create context and use context and provide the parent to provide and the child as a consumer but the best option pass as a prop
I would recommend you use the parent-child nature of react in order to handle this. Since I'm not sure the relationship between the other module and this module, I'll provide a rough skeleton.
In a parent component:
export default function ParentComponent(){
const [status, updateStatus] = useState("")
return(
<TextView updateParent={updateStatus}>
</TextView>
)
}
Then in your child component:
const handleChange = (event) => {
setValue(value);
props.updateParent(value);
};
If they are siblings, I would use a parent component and then pass the state down to the sibling. Otherwise, as appropriate, use this parent child relationship to pass and update state.
HTH

React Native load data from API using hooks

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.

react native 100+ items flatlist very slow performance

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/>

Categories