react icon indicating copy to clipboard operation
react copied to clipboard

[DevTools Bug]: Phantom re-renders on sibling <label> components

Open jindong-zhannng opened this issue 1 year ago • 7 comments

Website or app

https://stackblitz.com/edit/react-devtools-bug?file=src%2FApp.jsx

Repro steps

  1. Start profiler
  2. Input into "Component with state"
  3. Both of "ComponentWithState" and "AnotherReactComponent" were re-rendered will be shown in the report but why?

image

How often does this bug happen?

Every time

DevTools package (automated)

No response

DevTools version (automated)

No response

Error message (automated)

No response

Error call stack (automated)

No response

Error component stack (automated)

No response

GitHub query string (automated)

No response

jindong-zhannng avatar Oct 18 '24 08:10 jindong-zhannng

I have been able to reproduce this issue too with very similar simple examples

noahpro99 avatar Oct 18 '24 15:10 noahpro99

Bumped.

I also can easily reproduce this with a Vite template with [email protected] and [email protected].

Run npm create vite and change App.jsx like following:


const ViteIcon = () => {
  return (
    <div><a href="https://vitejs.dev" target="_blank">
      <img src={viteLogo} className="logo" alt="Vite logo" />
    </a></div>
  )
}

const ReactIcon = () => {
  return (
    <div>
      <a href="https://react.dev" target="_blank">
        <img src={reactLogo} className="logo react" alt="React logo" />
      </a>
    </div>
  )
}

const Counter = () => {
  const [count, setCount] = useState(0)
  return (
    <div className="card">
      <button onClick={() => setCount((count) => count + 1)}>
        count is {count}
      </button>
      <p>
        Edit <code>src/App.tsx</code> and save to test HMR
      </p>
    </div>
  )
}

function App() {
  return (
    <>
      <div>
        <ViteIcon />
        <ReactIcon />
      </div>
      <Counter />
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

Now everytime I click the button, both ReactIcon and ViteIcon are re-rendered but they should not, right? (I use Highlight updates when components render. option to check this, not a profiler.)

Weird thing is, when you remove div around ReactIcon and ViteIcon these re-rendering won't happen.

I'm sure I hadn't observed this behaviour a month ago. I'm really confused about this 😕

Can anyone shed light on this?

craftgear avatar Nov 02 '24 08:11 craftgear

Here is an additional Info.

This happens on Chrome Extension DevTools version: 6.0.1-c7c68ef842 and Firefox addon DevTools version: 6.0.0-d66fa02a30.

But not on Firefox addon DevTools version: 5.3.1-ccb20cb88b.

I hope this might be a bit of help.

craftgear avatar Nov 04 '24 22:11 craftgear

Here is an additional Info.

This happens on Chrome Extension DevTools version: 6.0.1-c7c68ef842 and Firefox addon DevTools version: 6.0.0-d66fa02a30.

But not on Firefox addon DevTools version: 5.3.1-ccb20cb88b.

I hope this might be a bit of help.

Any idea about version of Chrome Extension without this problem?

jindong-zhannng avatar Nov 08 '24 08:11 jindong-zhannng

Any idea about version of Chrome Extension without this problem?

Unfortunately no, I don't keep old Chrome environments.

craftgear avatar Nov 09 '24 03:11 craftgear

No team members care about dev tools now?

jindong-zhannng avatar Dec 04 '24 10:12 jindong-zhannng

BTW, react-scan could be a good alternative. https://github.com/aidenybai/react-scan

I have difficulties to see re-rendering flash though 😅

craftgear avatar Dec 04 '24 11:12 craftgear

This is the same issue as in https://github.com/facebook/react/issues/31353 caused by the way how fragments are handled during re-rendering. See my comment in the linked issue for details.

V3RON avatar Jan 15 '25 15:01 V3RON

@V3RON I see this issue or a similar one when using only a single child in the updated component. React DevTools shows re-rendering for sibling components when no re-rendering seems to have occurred.

const Rerenders = () => {
  const [increment, setIncrement] = useState(0);
  useEffect(() => {
    requestAnimationFrame(() => {
      setIncrement(increment + 1);
    });
  }, [increment]);
  return <p>I rerender all of the time</p>;
};

const ShouldNotRerender = () => {
  console.log(
    "ShouldNotRerender actually rendered" // Appears 2 times per direct or memoized use with strict mode, never any more
  );
  return <p>I should not rerender</p>;
};
const ShouldNotRerenderMemo = React.memo(ShouldNotRerender);

export default function App() {
  return (
    <div>
      {/* 🔃 Should rerender */}
      <Rerenders />
      {/* ✅ Okay */}
      <ShouldNotRerender />
      {/* ✅ Okay */}
      <ShouldNotRerenderMemo />
      <div>
        {/* ⚠️ DevTools shows rerenders */}
        <ShouldNotRerender />
        {/* ⚠️ DevTools shows rerenders */}
        <ShouldNotRerenderMemo />
      </div>
    </div>
  );
}

Screenshot showing the React DevTools rerender overlay with the rerending component and the two non-rerendering components inside a div

The profiler shows a hook change in <Rerenders> (accurate), the first <ShouldNotRerender> and its memoized version not rerendering (accurate), but then it shows <ShouldNotRerender> and its memoized version rerendering when inside the <div> (not accurate).

The profiler claims it's due to the parent component rerendering, but <App> didn't rerender, and the console.log() doesn't fire outside of initial render from any of the <ShouldNotRerender> uses.

Screenshot of the React DevTools profiler with the above information

So I looked to see what the profiler would say about the direct parent <div>, and interestingly enough, everything changes when trying to run the profiler unfiltered.

  1. Run the profiler with Hide components where... type equals dom nodes (e.g. <div>) enabled (required for some reason, another bug?)
  2. Run the profile again with the filter disabled

During and after the unfiltered profiler run, the overlay will show you just p is re-rendering, which doesn't feel technically correct to me, but at least it's the right component.

Screenshot showing the React DevTools rerender overlay only showing updates for a p inside the rerendering component

In fact, the profiler for the unfiltered run doesn't even have <Rerenders>'s <p> tag, and attributes the rerender to <Rerenders> due to the hook change (accurate).

So the overlay doesn't feel accurate, but on the positive side, the profiler is now accurate! <ShouldNotRerender> (memoized or not) and its parent <div> (if any) do not show any rerendering. This makes me believe that some phantom rerenders are arise from the filtering logic or something adjacent in DevTools.

Screenshot of the React DevTools profiler with the above information from the unfiltered run

Upon a refresh, the overlay will only show React components again even if you profile with the filter disabled until you profile with it enabled and then disabled.

Codesandbox

evelynhathaway avatar Mar 11 '25 19:03 evelynhathaway

facing the same issue...

roo12312 avatar Nov 13 '25 11:11 roo12312