i am trying to understand react memo and i created a simple interface in my react native app. the app consists of two elements:
MainApp.tsx -> controls the list of the user
User.tsx -> displays user object
my plan is to have all user information displayed on first render and each user should have some sort of "update" button which would case it to re-render. The user would be passed to the list item component along with a description. If the description changes, the whole list should be re-rendered. The current implementation looks like this:
mainapp:
// MainApp component
import React, { useState } from 'react';
import { StyleSheet, Button, SafeAreaView, FlatList } from 'react-native';
import User from './User';
export interface IUser {
name: string;
id: number;
age: number;
}
const initialUsers: IUser[] = [
{ id: 1, name: 'Ivan', age: 20 },
{ id: 2, name: 'Elena', age: 25 },
{ id: 3, name: 'John', age: 30 },
];
export const MainApp = () => {
const [users, setUsers] = useState<IUser[]>(initialUsers);
const [description, setDescription] = useState<string>(
'A passionate developer',
);
const updateUserAge = (userId: number) => {
setUsers(
users.map(user =>
user.id === userId
? { ...user, age: Math.floor(Math.random() * (100 - 20 + 1)) + 20 }
: user,
),
);
};
const updateDescription = () => {
setDescription(
(Math.floor(Math.random() * (100 - 20 + 1)) + 20).toString(),
);
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={users}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<User
user={item}
description={description}
onUpdateAge={updateUserAge}
/>
)}
/>
<Button onPress={updateDescription} title="Update Description" />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default MainApp;
user.tsx
import React, { memo } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { IUser } from './MainApp';
interface UserProps {
user: IUser;
description: string;
onUpdateAge: (userId: number) => void;
}
export const User = memo(
({ user, description, onUpdateAge }: UserProps) => {
console.log('Rendering User', user.name);
const handleUpdateAge = () => {
onUpdateAge(user.id);
};
return (
<View style={styles.container}>
<Text>Name: {user.name}</Text>
<Text>Age: {user.age}</Text>
<Text>Description: {description}</Text>
<Button onPress={handleUpdateAge} title="Update Age" />
</View>
);
},
(prevProps, nextProps) => {
return (
prevProps.user.age === nextProps.user.age &&
prevProps.description === nextProps.description
);
},
);
const styles = StyleSheet.create({
container: {
margin: 10,
padding: 10,
backgroundColor: '#eee',
},
});
export default User;
since the object reference stays the same, i specify what props to compare. When i click on the first element i get:
LOG Rendering User Ivan
which is correct and the whole list was not re-rendered, only one item is updated.
however, if i click on another list item after that i get this:
LOG Rendering User Ivan
LOG Rendering User Elena
For some reason two list items were updated and it keeps going if i click on another users. Can you help me understand why the list items are re-rendered?
passing user
‘s fields separately still produces the same problem:
https://snack.expo.dev/@denistepp/hot-orange-nachos
2
Answers
the problem was in
updateUserAge
actually. The dependency onusers
always created a new function causing a re-render. here is the fixed solution:considering the description prop:
description
changes you can just usememo
wrap and nothing else. in this case the main app render function would look like this:in both cases the renderItem could be simplified and look like this:
Remember
object
s andArray
s inside thememo
/useMemo
/useCallback
dependencies always will be different ’cause the check is by reference not by value. So you have to use whenever possible primitive values –bolean
,number
,string
–.Performance ⬆️
Second, try to move your
renderItem
component to a memorized function out of yourJSX return
block:This will let you know that every time
description
orupdateUserAge
changes your component will be re-rendered. If you want to avoid that, move thedescription
into theitem
itself.