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
Remember
objects andArrays inside thememo/useMemo/useCallbackdependencies 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
renderItemcomponent to a memorized function out of yourJSX returnblock:This will let you know that every time
descriptionorupdateUserAgechanges your component will be re-rendered. If you want to avoid that, move thedescriptioninto theitemitself.