8000 [Compiler]: skipped memoization due to potential modification after hook call · Issue #33050 · facebook/react · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Compiler]: skipped memoization due to potential modification after hook call #33050

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 of 4 tasks
szhsin opened this issue Apr 29, 2025 · 5 comments
Open
1 of 4 tasks

Comments

@szhsin
Copy link
szhsin commented Apr 29, 2025

What kind of issue is this?

  • React Compiler core (the JS output is incorrect, or your app works incorrectly after optimization)
  • babel-plugin-react-compiler (build issue installing or using the Babel plugin)
  • eslint-plugin-react-compiler (build issue installing or using the eslint plugin)
  • react-compiler-healthcheck (build issue installing or using the healthcheck script)

Link to repro

https://playground.react.dev/#N4Igzg9grgTgxgUxALhASwLYAcIwC4AEwBUYCAyhBgngBZoB2A5gDQEAmEl1djTBAXwIAzGFQIBySNQC0AGzQAjCQG4AOgw1wIDMIVIIAwqTxUAEhAgBrAgF4CACgBuAQzlQEASjsA+IhoICbV1Cdhc8FzsiAld3BEF1TQZAmBpYZOAAwIIsMSwARmQSMm4aemYHYjCIwU8WLMDciCwAJiLOUt4KqvDIgU8sgUShjQ0EAA8cfGiDYz1zSxsRhhABIA

Repro steps

Currently, the compiler memoizes the arguments of React hooks and regular functions when the arguments are non-primitive values. However, the behavior is inconsistent, and memoization is skipped when certain conditions are met:

  1. The arguments passed to a React hook or regular function are non-primitive values (e.g., an object).
  2. The argument object refers to another local variable, which is also non-primitive.
  3. Both the React hook and the regular function reference the same local variable when constructing their arguments.

Here is a minimal example:

const useCustomHook = (value) => {
  const data = { value };

  return {
    prop1: useSomething({ data }),
    prop2: doSomething({ data })
  };
};

In the example, memoization of the { data } argument passed to both useSomething and doSomething is skipped.

How often does this bug happen?

Every time

What version of React are you using?

19.1.0

What version of React Compiler are you using?

19.1.0-rc.1

@szhsin szhsin added Component: Optimizing Compiler Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Type: Bug labels Apr 29, 2025
@ObiwanKenobee

This comment has been minimized.

@POUSINRP

This comment has been minimized.

@POUSINRP

This comment has been minimized.

@josephsavona
Copy link
Contributor
josephsavona commented Apr 29, 2025

Thanks for posting. What's happening here is that the compiler sees a value being constructed and (potentially) modified later:

const data = { value };  // <-- value created here
// ...
     prop2: doSomething({ data }) // <-- value possibly modified here

React Compiler doesn't know what function doSomething(x) { ... } does to its argument x, and has to conservatively assume that doSomething() will modify that argument. For example, imagine if it did x.data.value++, which would increment the outer data.value in the example.

React Compiler always groups modification of values together w the code that creates the value (otherwise we might repeatedly mutate the same value, which is bad). But that doesn't work in this case, because there is also a hook call!

cosnt data = { value };
...
useSomething({ data });
...
doSomething({ data });

To memoize data we need to include all of these statements in a conditional. But we can't make the useSomething() conditional — that's a hook call, and hooks can't be conditional. So in this case the compiler conservatively skips memoization.

We're constantly working to improve our heuristics for edge cases like this, but there are some cases where we have to skip memoization out of an abundance of caution.

@josephsavona josephsavona changed the title [Compiler Bug]: inconsistent memoization of non-primitive arguments in hooks and functions [Compiler]: skipped memoization due to potential modification after hook call Apr 29, 2025
@josephsavona josephsavona removed the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Apr 29, 2025
@szhsin
Copy link
Author
szhsin commented Apr 29, 2025

Thanks for the explanation. It actually makes sense to skip memoization in this case, though it technically breaks the React rule of keeping renders pure, since doSomething involves mutation.

But I did notice that if the regular function call comes before the hook, memoization is still generated:

const useCustomHook = (value) => {
  const data = { value };

  return {
    // swap the order of the regular function call and hook
    prop1: doSomething({ data }),
    prop2: useSomething({ data }),
  };
};

Compiled output:

let data;
let t0;
if ($[0] !== value) {
  data = { value };

  t0 = doSomething({ data });
  $[0] = value;
  $[1] = data;
  $[2] = t0;
} else {
  data = $[1];
  t0 = $[2];
}
let t1;
if ($[3] !== data) {
  t1 = { data };
  $[3] = data;
  $[4] = t1;
} else {
  t1 = $[4];
}
const t2 = useSomething(t1);

It seems the assumption here is that if doSomething mutates data, that mutation is complete by the time the code reaches the call site for useSomething, so data can be treated as effectively immutable at that point.

However, memoization might break the code. For example, if doSomething mutates data (e.g. data.value++), and useSomething depends on that updated value, then memoization would skip the doSomething call, and the mutation along with it. As a result, useSomething would receive a different argument than it would without memoization.

So if there's a chance that hook arguments are modified by a regular function, it's probably safer to skip memoization consistently, regardless of the order of the hook and the function call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants
0