diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index 9fb91a4ac829b..c74ef64529a17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -222,6 +222,13 @@ function writeDependencyToInstructions( */ break; } + if (path.property === 'current') { + /* + * Prune ref.current accesses. This may over-capture for non-ref values with + * a current property, but that's fine. + */ + break; + } const nextValue = createTemporaryPlace(env, GeneratedSource); nextValue.reactive = reactive; instructions.push({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md new file mode 100644 index 0000000000000..bc5c3e4dd466f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.expect.md @@ -0,0 +1,89 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +/** + * We never include a .current access in a dep array because it may be a ref access. + * This might over-capture objects that are not refs and happen to have fields named + * current, but that should be a rare case and the result would still be correct + * (assuming the effect is idempotent). In the worst case, you can always write a manual + * dep array. + */ +function RefsInEffects() { + const ref = useRefHelper(); + const wrapped = useDeeperRefHelper(); + useEffect(() => { + print(ref.current); + print(wrapped.foo.current); + }); +} + +function useRefHelper() { + return useRef(0); +} + +function useDeeperRefHelper() { + return {foo: useRefHelper()}; +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +/** + * We never include a .current access in a dep array because it may be a ref access. + * This might over-capture objects that are not refs and happen to have fields named + * current, but that should be a rare case and the result would still be correct + * (assuming the effect is idempotent). In the worst case, you can always write a manual + * dep array. + */ +function RefsInEffects() { + const $ = _c(3); + const ref = useRefHelper(); + const wrapped = useDeeperRefHelper(); + let t0; + if ($[0] !== ref.current || $[1] !== wrapped.foo.current) { + t0 = () => { + print(ref.current); + print(wrapped.foo.current); + }; + $[0] = ref.current; + $[1] = wrapped.foo.current; + $[2] = t0; + } else { + t0 = $[2]; + } + useEffect(t0, [ref, wrapped.foo]); +} + +function useRefHelper() { + return useRef(0); +} + +function useDeeperRefHelper() { + const $ = _c(2); + const t0 = useRefHelper(); + let t1; + if ($[0] !== t0) { + t1 = { foo: t0 }; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js new file mode 100644 index 0000000000000..9a213fad5f447 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref-helper.js @@ -0,0 +1,27 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +/** + * We never include a .current access in a dep array because it may be a ref access. + * This might over-capture objects that are not refs and happen to have fields named + * current, but that should be a rare case and the result would still be correct + * (assuming the effect is idempotent). In the worst case, you can always write a manual + * dep array. + */ +function RefsInEffects() { + const ref = useRefHelper(); + const wrapped = useDeeperRefHelper(); + useEffect(() => { + print(ref.current); + print(wrapped.foo.current); + }); +} + +function useRefHelper() { + return useRef(0); +} + +function useDeeperRefHelper() { + return {foo: useRefHelper()}; +}