Skip to content

Conversation

@josephsavona
Copy link
Member

We currently model impure functions like Date.now() with an Impure effect, but the way this works is that the effect is considered an error if it's called at all. But we really want to reflect the fact that the value is impure, and is okay to be used outside of render. For example, useRef(Date.now()) should be fine.

So this PR changes the Impure effect to describe that an impure value flows into: Place. We flow impurity through the data flow graph during InferMutationAliasingRanges, including propagating this to the function's effects. So if a function expression returns a transitively impure value, that's expressed in our inferred signature for that function. Calling it propagates the impurity via our effect system.

We stop this propagation when reaching a ref, allowing useRef(Date.now()) or useRef(localFunctionThatReturnsDateNow()).

This lets us also model accessing a ref as an impure value - we just emit an Impure event for PropertyLoad/ComputedLoad off of a ref, and the above tracking kicks in.

A final piece is that we can also use our existing machinery to disallow writing to global values during render to handle writing to refs - except that we need to allow the lazy init pattern. So it probably makes sense to keep the ref validation pass, but scope it back to just handling writes and not reads.

This definitely needs more polish, the error messages would ideally be something like:

Error: cannot call impure function in render

Blah blah description of why this is bad...

foo.js:0:0
  <Foo x={x} />
          ^ This value reads from an impure function

foo.js:0:0
  x = Date.now();
      ^^^^^^^^ The value derives from Date.now which is impure

Ie show you both the local site (where impurity flows into render) and the ultiamte source of the impure value.

@meta-cla meta-cla bot added the CLA Signed label Dec 5, 2025
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Dec 5, 2025
Comment on lines +644 to +648
// Render the arguments
{
kind: 'Render',
place: '@rest',
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to add the Render effect

],
});

const EffectHookAliasing: AliasingSignatureConfig = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracting from useEffect so we can reuse for insertion/layout effects too

Comment on lines +2189 to +2190
if (prop.kind === 'JsxAttribute' && /^on[A-Z]/.test(prop.name)) {
continue;
}
effects.push({
kind: 'Render',
place: prop.kind === 'JsxAttribute' ? prop.place : prop.argument,
});
Copy link
Member Author

@josephsavona josephsavona Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider all props except obvious event handlers as having a render effect, but InferMutationAliasingRanges is smarter about how it applies render effects to functions

Comment on lines 9 to 11
const f = () => Math.random();
const ref = useRef(f());
return <div ref={ref} nonRef={nonRef} state={state} setState={setState} />;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs cleanup but yay, allowing using an impure value in a ref

@josephsavona josephsavona force-pushed the pr35298 branch 2 times, most recently from d7beb75 to 6ab3ca1 Compare December 5, 2025 21:08
We currently model impure functions like `Date.now()` with an `Impure` effect, but the way this works is that the effect is considered an error if it's called at all. But we really want to reflect the fact that the _value_ is impure, and is okay to be used outside of render. For example, `useRef(Date.now())` should be fine.

So this PR changes the Impure effect to describe that an impure value flows `into: Place`. We flow impurity through the data flow graph during InferMutationAliasingRanges, including propagating this to the function's effects. So if a function expression returns a transitively impure value, that's expressed in our inferred signature for that function. Calling it propagates the impurity via our effect system.

We stop this propagation when reaching a ref, allowing `useRef(Date.now())` or `useRef(localFunctionThatReturnsDateNow())`.

This lets us also model accessing a ref as an impure value - we just emit an `Impure` event for PropertyLoad/ComputedLoad off of a ref, and the above tracking kicks in.

A final piece is that we can also use our existing machinery to disallow writing to global values during render to handle writing to refs - except that we need to allow the lazy init pattern. So it probably makes sense to keep the ref validation pass, but scope it back to just handling writes and not reads.

This definitely needs more polish, the error messages would ideally be something like:

```
Error: cannot call impure function in render

Blah blah description of why this is bad...

foo.js:0:0
  <Foo x={x} />
          ^ This value reads from an impure function

foo.js:0:0
  x = Date.now();
      ^^^^^^^^ The value derives from Date.now which is impure
```

Ie show you both the local site (where impurity flows into render) and the ultiamte source of the impure value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants