Skip to content
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

[Compiler Bug]: eslint-plugin-react-compiler errors when updating initialization of ref.current #30782

Open
1 of 4 tasks
jeremy-code opened this issue Aug 21, 2024 · 9 comments
Labels
Component: Optimizing Compiler Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Type: Bug

Comments

@jeremy-code
Copy link

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/#N4Igzg9grgTgxgUxALhAHRFMCAEcA2AlggHYAuGA3GiTYQLYAOEMZOwOWCASggGY4Avjj4wI9HBhgIAhnAohqtEnAgkwbAKoA5AJJ6AKroCCAGV0AtAKIARHAF4cAZQCe9AEYR8ACgw79ukZmlrYYAJRKNAgAHsyseGoanNgA8iq4jgA8BgA0AHzehCSEZIQy+ABq5VAIyDgGOAA+ON7eYQ559WHt9p3ANDgJ6mzSAo5cvAKZ9U04ZC6MCBAC-oYm5tZ2nd6rgeshNhE0AziEAt6jAHRwsNLkDvaOu0Ebtu39JIODVzcwd2yOeaLZanYqlcpVfA1B6ODB8KAqUpqDA4AD8oJKZUq1QQbRwdSKmIhOKUg0Ex0+OGkZFgnx+t1IZCUgkiJBicTYABN+DIoPg2PDEYQ1DgALIuYyMRh4j7fBA0mCfTKcwgANzyAAkEPh8BAcAB1Fj4TmZAD0KvVzJoIEEQA

Repro steps

  1. Initialize useRef with some dummy value (e.g. null, Symbol, undefined, etc.) to be changed after initialization/during render.
  2. Update ref.current by checking whether it is equivalent to its initial condition (as per documentation: useRef#Avoiding recreating the ref contents)
  • To solve it, you may initialize the ref like this instead:

function Video() {
  const playerRef = useRef(null);
  if (playerRef.current === null) {
    playerRef.current = new VideoPlayer();
  }
  // ...
  • Normally, writing or reading ref.current during render is not allowed. However, it’s fine in this case because the result is always the same, and the condition only executes during initialization so it’s fully predictable.

  1. eslint-plugin-react-compiler gives error
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)

How often does this bug happen?

Every time

What version of React are you using?

19.0.0-rc-1d989965-20240821

@jeremy-code jeremy-code added Component: Optimizing Compiler Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Type: Bug labels Aug 21, 2024
@jeremy-code
Copy link
Author

Related: #30716

@parthnegi21
Copy link

Try this

import { useRef, useState, useEffect } from "react";

const UNINITIALIZED = Symbol("UNINITIALIZED");

export const useOnce = <T,>(initialValue: T | (() => T)) => {
const ref = useRef < T | typeof UNINITIALIZED > (UNINITIALIZED);
const [value, setValue] = useState < T | typeof UNINITIALIZED > (UNINITIALIZED);

useEffect(() => {
if (ref.current === UNINITIALIZED) {
const resolvedValue = typeof initialValue === "function" ? initialValue() : initialValue;
ref.current = resolvedValue;
setValue(resolvedValue);
} else {
setValue(ref.current);
}
}, [initialValue]);

return value;
};

export default function MyApp() {
return

Hello World
;
}

State Initialization: Added a state value to store the initialized value.

useEffect: The ref is now initialized inside the useEffect hook, which sets the value both in the ref and in the state.

Return Value: The hook returns the state value, ensuring that the initial render doesn't access ref.current

@josephsavona
Copy link
Contributor

Thanks for posting. This is a known limitation of the new linter.

@hlege
Copy link

hlege commented Sep 1, 2024

i also run into the problem in a different scenario:
when passing the ref object to a custom hook the compiler is not optimizing my code. if i remove the ref access it works as expected.

// pass the ref object to a custom hook and it is preventing the optimizations.  
const mergedRefs = useMergedRef<HTMLDivElement>(ref, setPopperElement);

@iahu
Copy link

iahu commented Sep 2, 2024

Do you deprecated to use useRef? if not, how to use it?

const panelRef = useRef<HTMLDivElement>(null);
const width = panelRef.current?.clientWidth ?? 256; // eslint error here

@alisherks
Copy link

alisherks commented Sep 2, 2024

Is there any available workaround for this? This limitation also affects the useImperativeHandle.

@alisherks
Copy link

To anyone encountering optimization issues with the usage of useImperativeHandle, you can place the forwardedRef inside a useState hook.

const [refs] = useState(() => {
    return { forwardedRef };
});

This works against latest experimental release (0.0.0-experimental-4e0eccf-20240830). It probably breaks the rules, but so far didn't encounter serious issues.

@nkalpakis21

This comment was marked as spam.

@ravicious
Copy link

I think this is addressed in 19.0.0-beta-8a03594-20241020. I have a hook that looks like this:

export function useLogger(name: string) {
  const loggerRef = useRef<Logger>(null);
  if (loggerRef.current === null) {
    loggerRef.current = new Logger(name);
  }

  return loggerRef.current;
}

The linter rule complains only about that .current in the return statement. However, the conditional needs to have exactly this form of fooRef.current === null, otherwise the rule will report a false positive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component: Optimizing Compiler Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug Type: Bug
Projects
None yet
Development

No branches or pull requests

8 participants