Reputation: 6770
Is there a way to get ref
from a React 16 portal. I tried the following approach but it doesn't seem to work:
const Tooltip = props => (
ReactDOM.createPortal(
<div>{props.children}</div>,
// A DOM element
document.body
)
);
class Info extends React.Component {
render() {
return (
<Tooltip
ref={ el => this.tooltip = el }
>
My content
</Tooltip>
);
}
componentDidMount() {
console.log(this.tooltip); // undefined
}
}
I need the ref
in order to dynamically calculate the element final position!
https://codepen.io/anon/pen/QqmBpB
Upvotes: 11
Views: 10781
Reputation: 11865
If you want to do it with hooks, you can also do
import { createPortal } from 'react-dom'
import React, { useRef } from 'react'
const Portal = ({ children }) => {
const portal = useRef(document.createElement('div'))
return createPortal(children, portal.current)
}
Also, in your exact example, you would need to use forwardRef
since you can't pass refs
to children without it.
const Tooltip = forwardRef((props, ref) => (
createPortal(
<div ref={ref}>{props.children}</div>,
// A DOM element
document.body
)
));
const Info = () => {
const ref = useRef()
return (
<Tooltip ref={ref}>
My content
</Tooltip>
)
}
This last bit is not tested, but I'm pretty sure it'll work.
Upvotes: 3
Reputation: 29
You need to use this.tooltip.props.children
in App component and use the following code for the Tooltip component:
const appRoot = document.getElementById('app-root');
const tooltipRoot = document.getElementById('tooltip-root');
class Tooltip extends Component {
constructor(props) {
super(props);
// Create a div that we'll render the Tooltip into
this.el = document.createElement('div');
}
componentDidMount() {
// Append the element into the DOM on mount.
tooltipRoot.appendChild(this.el);
}
componentWillUnmount() {
// Remove the element from the DOM when we unmount
tooltipRoot.removeChild(this.el);
}
render() {
// Use a portal to render the children into the element
return ReactDOM.createPortal(
// Any valid React child: JSX, strings, arrays, etc.
this.props.children,
// A DOM element
this.el,
);
}
}
class App extends React.Component {
componentDidMount() {
console.log(this.tooltip.props.children);
}
render() {
return (
<div>
<Tooltip ref={ el => this.tooltip = el }>
My content
</Tooltip>
</div>
);
}
}
ReactDOM.render(<App />, appRoot);
Working demo https://codepen.io/jorgemcdev/pen/aLYRVQ, based on Dan Abramov code sample https://codepen.io/gaearon/pen/jGBWpE
Upvotes: 0
Reputation: 6770
ReactDOM.createPortal
returns a ReactPortal instance, which is a valid ReactNode but not a valid DOM element. At the same time createPortal
will honour the component context. So I moved the function call to be inside the render method and it solved the issue.
class Info extends React.Component {
render() {
// I moved the portal creation to be here
return ReactDOM.createPortal(
// A valid DOM node!!
<div ref={ el => this.tooltip = el }>{props.children}</div>,
// A DOM element
document.body
);
}
componentDidMount() {
console.log(this.tooltip); // HTMLDivElement
}
}
Upvotes: 8