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?
CodePudding user response:
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 just simply rendered child2
without that condition instead.
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.
CodePudding user response:
"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.
CodePudding user response:
useMemo
is perfectly sensible here as it memoizes the result (the JSX). As you say, useCallback
memoizes the function not the result.