Skip to content

[Compiler Bug]: Functions defined in render that return primitives don't have usages memoized #33502

Open
@jaaamesey

Description

@jaaamesey

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/#N4Igzg9grgTgxgUxALhASwLYAcIwC4AEwBUYCAyngIZ4IEC+BAZjBBgQOQwJVx4cBuADoA7UUygi+aCCIIAhKgBMAFAEoioggTiywhANq7JtGABoCZPAGFoI0wF0CAXhJlKNBCoAMa4WLlmSWlZAgQADywEETA0ADcEW2woajwZEXVNQO0mXAIVABsEQjQXAm8BAlKAHgIATga6yrQAahaNYC1tBi7tbjxYOQBmf216US6AekmCJKwUhCUw3gALAm4RJQQYAEIu3RjCCKiY+IQANSoCqDpXY+jYhLmUmnT1fy7+wYJqgCMUvChWTWApoOAAa2cwEyzgAfJZirYTNsVHI4QQ5C0CABGNT0WFI+zIIjGezbRjAe6nBKXa4IejVSb-PCAkSw0TjAISKRpUIAcQgEFUHX2ekMpNMFishMcZVIFFSXl8H0C3JCcipj0SbHmqTeIuyzDyhWKVTKFTNtUaTSqbQN3R6hq+MDkxDiVxuxKGDFGjqmMwAchBCLpkrQljw4GsNltdqLDmFIg8zrSbmVNWdnnrZO8JoFnXI-gCgSIQWDIdCNOjpXZTKiXPDMTi8QTa8TgBLyUQMzSPQgAHTuukMpnFtkcvNq3lyACCBUgAqFmU6gQO+gIRlr2yliK3MCcrnlHloPj8ee00wIQZDOoWEdW62isb2q7FiZOWtTt3fyaet+zGRngEfTFN8RYsiWZYQlCMLwjWyIwPW6JNri+Iyu2nYwBSPYXH2I7Mqy7IiJy4jBNOP7UtqYavDm9pGjA+RFCU5rND89SNM0dpZGMnygS6BAjByID0EAA

Repro steps

Loving the compiler so far! Here's a bit of an inconsistency I've discovered - apologies if it's been brought up elsewhere.

If a function:

  • is defined in the body of a component
  • returns a primitive value, instead of an object

calls to it won't be memoized, even if it's particularly expensive.

Here's an example of what I mean: expensiveComputation is called every render (i.e. every time the button is clicked).

function Bad() {
  const [counter, setCounter] = useState(0);

  function expensiveComputation() {
    for (let i = 0; i < 9999; i++) {
    }
    return 3;
  }

  // Computed each render!
  const expensiveValue = expensiveComputation();

  return <button onClick={() => setCounter(n => n + 1)}>Count: {counter} {expensiveValue}</button>
}

What's interesting is that the function reference itself is actually memoized - it's just calls to it that aren't.

  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = function expensiveComputation() {
      for (let i = 0; i < 9999; i++) {}
      return 3;
    };
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  const expensiveComputation = t0;

Again, this issue does not occur if the function returns an object:

function Good() {
  const [counter, setCounter] = useState(0);

  function expensiveComputation() {
    for (let i = 0; i < 9999; i++) {
    }
    return { value: 3 };
  }

  // Not computed each render!
  const expensiveValue = expensiveComputation();

  return <button onClick={() => setCounter(n => n + 1)}>Count: {counter} {expensiveValue.value}</button>
}

Or if the function is defined outside the component:

function AlsoGood() {
  const [counter, setCounter] = useState(0);

  // Not computed each render!
  const expensiveValue = expensiveComputation();

  return <button onClick={() => setCounter(n => n + 1)}>Count: {counter} {expensiveValue}</button>
}

function expensiveComputation() {
  for (let i = 0; i < 9999; i++) {
  }
  return 3;
}

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.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugType: Bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions