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

Bug: Ref is set after componentDidMount (when mixing Class and Functional components) #29897

Closed
ifrost opened this issue Jun 14, 2024 · 3 comments
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@ifrost
Copy link

ifrost commented Jun 14, 2024

React version: 18.2.0

Steps To Reproduce

import React, { PureComponent, useCallback, useEffect } from 'react';

export function App(props) {
  const refCallback = (ref) => {
    console.log("Reference received")
  };

  return (
    <div ref={refCallback}>
        <ClassComponent  />
        <FunctionalComponent />
    </div>
  );
}

class ClassComponent extends PureComponent {
  componentDidMount() {
    console.log("ClassComponent mounted")
  }
  render() {
    return "ClassComponent"
  }
}


function FunctionalComponent() {
  useEffect(() => {
    console.log("FunctionalComponent mounted")
  })
  return "FunctionalComponent"
}

Link to code example: https://playcode.io/1905766

The current behavior

Console logs:

ClassComponent mounted
Reference received
FunctionalComponent mounted

ClassComponent is mounted before reference is received. It's different if a functional component is used.

The expected behavior

I'm not sure if this is expected behavior but I was expecting to receive a ref before components are mounted.

Reference received
ClassComponent mounted
FunctionalComponent mounted
@ifrost ifrost added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Jun 14, 2024
@Riyazul555
Copy link

The behavior you're observing is a result of how React handles refs and the component lifecycle. Let's break down what's happening step by step:

Rendering Phase:

React begins rendering the App component.
It encounters the div with the ref attribute and schedules the refCallback to be executed after the div is rendered.
Mounting Phase:

React mounts the children of the App component.
ClassComponent is mounted, triggering its componentDidMount lifecycle method.
FunctionalComponent is mounted, triggering its useEffect hook.
Ref Assignment:

After the rendering is complete, React assigns the ref and calls the refCallback.
In a functional component, the useEffect hook runs after the component has rendered and the DOM has been painted. This aligns more closely with the timing of ref assignments.

Order of Events
In your App component, the ClassComponent and FunctionalComponent lifecycle methods (componentDidMount and useEffect) are invoked before the refCallback for the parent div is called. This is why you're seeing the logs in the order you described.

Expected vs. Actual Behavior
The actual behavior:

Copy code
ClassComponent mounted
Reference received
FunctionalComponent mounted
What you expected:

Copy code
Reference received
ClassComponent mounted
FunctionalComponent mounted
Why This Happens
Class Components: The componentDidMount lifecycle method is called after the component is rendered, but before the refs are assigned.
Functional Components: The useEffect hook runs after the paint phase, aligning with the ref assignments but still following class component lifecycle hooks.
Adjusting Expectations
The current behavior is as expected per React's lifecycle. The refCallback is called after the componentDidMount and useEffect hooks.

@Riyazul555
Copy link

Hey @ifrost can you elaborate a little in which part of existing code/file you want this change ??

@ifrost
Copy link
Author

ifrost commented Jun 17, 2024

In your App component, the ClassComponent and FunctionalComponent lifecycle methods (componentDidMount and useEffect) are invoked before the refCallback for the parent div is called.

ClassComponent.componentDidMount is invoked before the refCallback is called. FunctionalComponent/useEffect callback is called after the refCallback is called.

Hey @ifrost can you elaborate a little in which part of existing code/file you want this change ??

I was expecting more consistency on timing but it seems it's kind of as you said - an expected behavior. useEffect is non-blocking and scheduled to run later, while componentDidMount is run during commit phase. In that case componentDidMount behaves more like useLayoutEffect. Thanks for the explanation, I will close the issue.

@ifrost ifrost closed this as completed Jun 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests

2 participants