Skip to content

[Compiler Bug]: React Compiler incorrectly hoists inner function outside closure, breaking variable reference #35342

@purpletortoise-choi

Description

@purpletortoise-choi

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-hooks (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/#N4Igzg9grgTgxgUxALhASwLYAcIwC4AEwBUYCAygJ4B2cAogB54IzUCGANuXrggQL4EAZjAgYCAchgI2cPBIA61JQgY58BOBGphCcaW2YBxDhABGnbrwIBeAgB4AKgD4AFGmpo8aTgDVOUAjIBI4AlLbOREoEBBwIhLq8wY62BB5ePhz+HIFK0ZraurFougjULGCp5QDuBOTx9q7hNpEAbhBoACZuoXnUMVo6CVBmYPpoZnx2rhwlzOUwwU0RBO1dzZHA+TGzpQtgAHRsnZ0zc2Usvf0xBNJ4sP3LLVHXN8V7FQedCHHMZx8wK5vfj5EHKa6DIpkPBWaSpVxQLCdQwsJauLDSVrBNjUSgbAg4vEEAA+BNx+K2rzSQgIrjwlCwCAgNMRyOYMFsNjsEiEUFo3m0EnClLeMUScLsrJRMFc4oQQJugh+ZBeooIctSUvZ2wEfTeu3mnyEuDosgAFq5XHAzPjrU0FWD8pDCABzeKwqa0ik6u4PdU8aSgvUFIYkMge+HeqnO-0ozXhmj0JgsdhcAMIJxuMAjMYwCYIAA0BDdMPTVx1Meh3Dj0y1qNp6MxyXxYRJISjaqr6YRSOlCpijqpvtYBAA2roUUWuyiALoEyrOoPgmLD-rEUgUdNFkseqfu9O66hgkAFkCDIRoF0odDYXCEemMogEAAKORdHgA8lgBToBMJROIEgWJMHAALRYG+HigQYcigVo2BoHEMAAPSdHMEgANx9K4IoEMhyHwVgiGGGg2gALIQN8wQKCAnAcDRSiCGAJFgBeCCVK+UDvtQX4-mAoQYSe4BmhA1QAJLUOyqZgCgQicGQ-BAA

Repro steps

Hi,

I found an issue while adopting React Compiler and wanted to report it.

Original Source Code

import { useSyncExternalStore } from 'react'

export const createGlobalStore = <T>(initialValue: T) => {
  let store: T = initialValue

  const listeners = new Set<() => void>()

  const subscribe = (listener: () => void) => {
    listeners.add(listener)
    return () => {
      listeners.delete(listener)
    }
  }

  const setStore = (updater: ((prev: any) => any) | any) => {
    if (typeof updater === 'function') {
      store = updater(store)
    } else {
      store = updater
    }

    listeners.forEach((cb) => cb())
  }

  const getStore = () => {
    return store
  }

  const useStore = () => {
    const state = useSyncExternalStore<T>(subscribe, getStore)

    const setState = (updater: ((prev: T) => T) | T) => {
      setStore(updater)
    }

    return [state, setState] as const
  }

  return { useStore, getStore, setStore }
}

Compiled Output

import __vite__cjsImport0_react_compilerRuntime from
"/node_modules/.vite-bizprofile-webview/deps/react_compiler-runtime.js?v=e0557d73";
const _c = __vite__cjsImport0_react_compilerRuntime["c"];
import __vite__cjsImport1_react from "/node_modules/.vite-bizprofile-webview/deps/react.js?v=e0557d73";
const useSyncExternalStore = __vite__cjsImport1_react["useSyncExternalStore"];
export const createGlobalStore = (initialValue) => {
    let store = initialValue;
    const listeners = /* @__PURE__ */
    new Set();
    const subscribe = (listener) => {
        listeners.add(listener);
        return () => {
            listeners.delete(listener);
        }
        ;
    }
    ;
    const setStore2 = (updater) => {
        if (typeof updater === "function") {
            store = updater(store);
        } else {
            store = updater;
        }
        listeners.forEach( (cb) => cb());
    }
    ;
    const getStore = () => {
        return store;
    }
    ;
    const useStore = () => {
        const $ = _c(3);
        if ($[0] !== "fa62d2894ba91e5b72b8903a58ed08db2e221dc337d30b892bb5c3f5e1dd73b5") {
            for (let $i = 0; $i < 3; $i += 1) {
                $[$i] = Symbol.for("react.memo_cache_sentinel");
            }
            $[0] = "fa62d2894ba91e5b72b8903a58ed08db2e221dc337d30b892bb5c3f5e1dd73b5";
        }
        const state = useSyncExternalStore(subscribe, getStore);
        const setState = _temp;
        let t0;
        if ($[1] !== state) {
            t0 = [state, setState];
            $[1] = state;
            $[2] = t0;
        } else {
            t0 = $[2];
        }
        return t0;
    }
    ;
    return {
        useStore,
        getStore,
        setStore: setStore2
    };
}
;
function _temp(updater) {
    setStore(updater);
}

Problem

The compiler hoists the setState function to module scope as _temp, but:

1. The original setStore variable inside the closure is renamed to setStore2
2. The hoisted _temp function still references setStore (the original name)
3. Since _temp is outside the closure, it cannot access setStore2
4. This results in ReferenceError: setStore is not defined at runtime

This error occurs in both local development and production builds.

Environment

- vite: 7.2.2
- react: 19.2.0
- react-dom: 19.2.0
- babel-plugin-react-compiler: 1.0.0

### How often does this bug happen?

Every time

### What version of React are you using?

19.2.0

### What version of React Compiler are you using?

19.2.0

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