How to find potential unnecessary re-renders in React
If you are a React developer. you should know React does UI updates when the component's state and props change. But some renders could be unnecessary.
Let's take one simple example to show unnecessary re-render.
if you run the code below, you will notice console log inside <Item/> component re-render every time we type something inside the input.
import React from 'react';
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
StatusBar,
TextInput,
} from 'react-native';
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
];
const Item = ({ title }) => {
console.log('RENDER');
return (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
};
const App = () => {
const [text, onChangeText] = React.useState('');
const [number, onChangeNumber] = React.useState(null);
const renderItem = ({ item }) => <Item title={item.title} />;
return (
<SafeAreaView style={styles.container}>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
/>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
export default App;
(Expo Snack: https://snack.expo.dev/@nazrdogan/unnecessary-re-render-flatlist)
We can solve the re-render problems using memo and useCallback etc... But this is not the topic of this post. Sometimes it's very hard to find out why it's re-rendered. There is a perfect package for this problem. why-did-you-render
Why Did you Render package
Why Did You Render is a package that helps notify why a component is re-rendering. WDYR doing this by monkey patching.
Setup
Installation is very easy
npm install @welldone-software/why-did-you-render --save-dev
or
yarn add --dev @welldone-software/why-did-you-render
Then create wdyr.js
file and import it as the first import in your application.
wdyr.js
:
import React from 'react';
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
After setup. when you debug the same code you will see potential unnecessary re-renders. I saw renderItem calling unnecessarily.
I fixed the rendering problem using (code below) React.memo. Here is the solution. It can be improved more but like I said before that's not the topic :D you will see it won't throw any log.
import React from 'react';
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
StatusBar,
TextInput,
} from 'react-native';
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
];
const Item = ({title}) => {
console.log('RENDER');
return (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
};
const List = React.memo(data => {
const renderItem = ({item}) => <Item title={item.title} />;
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
);
});
const App = () => {
const [text, onChangeText] = React.useState('');
return (
<SafeAreaView style={styles.container}>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
/>
<List data={DATA}></List>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
export default App;