Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reused React element prevents component updates

Tags:

reactjs

Foo is a component that should be rendered only once. This can be used for performance optimization, although this is purely theoretical question that doesn't address any specific coding problem.

This can be achieved by using shouldComponentUpdate or pure components, this is a recommended way to do this:

const Foo = () => <p>{Math.random()}</p>;
const FooOnce = React.memo(Foo);
const Bar = () => {
  const [, update] = useState();

  useEffect(() => {
    setTimeout(() => {
      update({});
    }, 500);
  }, []);

  return <>
    <FooOnce/>
  </>;
};

Or by keeping a reference to React element object and reusing it:

const fooOnce = <Foo/>;
const Bar = () => {
  const [, update] = useState();

  useEffect(() => {
    setTimeout(() => {
      update({});
    }, 500);
  }, []);

  return <>
    {fooOnce}
  </>;
};

That a component isn't re-rendered when element object is reused is intuitive but I know this from my own experience and not from official sources. This may result in less createElement calls than in pure components, so this could be considered a benefit.

Is this behaviour documented and expected in all React versions?

Are there reasons to not reuse elements this way to prevent component updates?

like image 582
Estus Flask Avatar asked May 07 '19 10:05

Estus Flask


1 Answers

Is this behaviour documented and expected in all React versions?

No, I did not find documentation stating this behavior.

What I found by digging into react source code and setting some breakpoints is that by saving and reusing the createElement result (const fooOnce = <Foo/>;) you effectively will use the exact same props object everytime, while calling createElement in each render creates a new props object everytime.

Currently, this does make a difference because of the following condition in function beginWork from the fibers engine: if (oldProps !== newProps || hasLegacyContextChanged())

when the props object has the same identity, react immediately considers that there is no work to be done for this element (and does not even call render).

This seems fairly probable to me to keep working, but of course there is no guarantee that this optimisation will always be in place with this exact behaviour.

the code I'm refering to is here: https://github.com/facebook/react/blob/c7398f33966c4fedcba2c48e915b379e8f334607/packages/react-reconciler/src/ReactFiberBeginWork.js#L2119

when you use just the normal you will end up calling render, and since the output is different it will update the DOM.

when you use React.memo, the props will not have the same identity, but then react will enter the memo component case (https://github.com/facebook/react/blob/c7398f33966c4fedcba2c48e915b379e8f334607/packages/react-reconciler/src/ReactFiberBeginWork.js#L2377) and do a shallowEquals comparison (https://github.com/facebook/react/blob/c7398f33966c4fedcba2c48e915b379e8f334607/packages/react-reconciler/src/ReactFiberBeginWork.js#L487)

Are there reasons to not reuse elements this way to prevent component updates?

This can change, so I would not use this in production code.

Besides that reason, I would avoid this because the decision if the component updates gets out of the component itself, since now to use it you need to change the consumer code, instead of just changing code on the component or adding a wrapper to it.

like image 154
Tiago Coelho Avatar answered Nov 18 '22 11:11

Tiago Coelho