Home > Blockchain >  React - useCallback vs useMemo for JSX elements
React - useCallback vs useMemo for JSX elements

Time:05-23

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.

  • Related