react: ReactDOM.render()/unstable_renderIntoContainer() doesn't return instance if called during an update

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

ReactDOM.render and ReactDOM.unstable_renderSubtreeIntoContainer no longer return created React component instances

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/84v837e9/).

this.eParentElement = document.createElement('div');
const ReactComponent = React.createElement(this.reactComponent, params);
this.componentRef = ReactDOM.render(ReactComponent, this.eParentElement);

What is the expected behavior?

After the steps above this.componentRef should be an instance just created - it is now null with React 16 beta.

It’s entirely possible that I should be doing something different now, but if so it’s not clear what that should be

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React 16 beta Chrome OSX

thanks

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 2
  • Comments: 27 (8 by maintainers)

Commits related to this issue

Most upvoted comments

if portals are the way forward for this sort of thing, where can I find documentation/information on how these might be used?

render() {
  return ReactDOM.createPortal(MyComponent, domNode);
}

You can mix that with regular elements too, e.g.

<div>
  <button />
   {ReactDOM.createPortal(<MyComponent />, domNode)}
</div>

I reproduced it! https://jsfiddle.net/x7c7bdh0/

This only happens when ReactDOM.render is called during an update (e.g. in componentDidMount).

Alternatively, is there a way to get the instantiated component later? In our particular use case we don’t necessarily need it immediately - some point later might be acceptable

There technically is although it’s a bit weird.

ReactDOM.render(<MyComponent />, node, function() {
  console.log(this); // instance
});

Note that this won’t work with arrow functions.

Or more familiar:

ReactDOM.render(<MyComponent ref={instance => {
  if (instance) {
    // do something
  }
}} />, node);

I believe the issue is related to how Fiber tracks scheduled work. Specifically: https://github.com/facebook/react/blob/master/src/renderers/shared/fiber/ReactFiberScheduler.js#L1408-L1420

When the root ReactDOM.render call is made, no work is scheduled so performWork is called, which eventually sets the child property on the Fiber. But when ReactDOM.render is called in componentDidMount, there’s already work in progress. So that !isPerformingWork check evaluates to false and the work isn’t scheduled. That means performWork isn’t called and child is never set.

This sounds correct to me. I’m not sure how to fix this. I think @acdlite should have the most context on this.

We currently force top level renders into a synchronous mode for compatibility with this case normally. As a legacy mode.

The core issue is that we don’t want to support reentrant renders. That’s why render is not synchronous in life-cycle methods. It’ll return what has already been rendered, which initially won’t be anything yet.

The idea is that these use cases should ideally switch to using Portals instead. Perhaps we should warn about these use cases and recommend switching to Portals?