Reputation: 2570
This is my react app component structure
<App>
<Body>
<Top>
### //<- Position 1
</Top>
<Middle>
<MyComponent/>
</Middle>
</Body>
</App>
I want the <MyComponent/>
to render at Position 1
anytime it is imported.
I have tried ReactDom.createPortal
but i keep getting Target container is not a DOM element
. If this is not possible how best can i achieve a similar setup.
What I have tried
//Top.js
<Top>
<div id="top-render"/>
</Top>
//MyCustomElement.js
class MyCustomElement extends Component{
render(){
return (<div>Demo Text</div>)
}
}
export default props => ReactDOM.createPortal(
<MyCustomElement />,
document.getElementById('top-render'),
);
//Middle.js
<Middle><MyCustomElement/></Middle>
Upvotes: 2
Views: 1940
Reputation: 66
You are getting the Target container is not a DOM element
error, because by the time execution reaches your ReactDOM.createPortal()
call, the DOM node that document.getElementById('top-render')
is attempting to retrieve, hasn't yet been written to the DOM.
This is because by that time, React is yet to make a successful pass through your component tree. i.e. React is yet to successfully create a virtual representation of everything in the component tree, and as a result, nothing has been written to DOM yet. So although you were expecting the top-render
div to be there in the DOM, it hadn't actually been written yet.
The solution is to create a DOM node owned by <MyComponent
, that will contain it's children (let's call this node el
), then pass that DOM node, as the target DOM element in your React.createPortal()
call (aka, it's second argument). Then when <MyComponent>
mounts, you simply retrieve the top-render
div and append el
to it. Like so:
const MyCustomComponent = () => {
return (<div>Demo Text: from "MyCustomComponent"</div>)
}
class MyComponent extends React.Component {
el = document.createElement("div");
componentDidMount() {
// saving this on the component so we can access it in `componentWillUnmount` later
this.targetEl = document.getElementById("top-render");
this.targetEl.appendChild(this.el);
}
componentWillUnmount() {
this.targetEl.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(<MyCustomComponent />, this.el);
}
}
I put together a working example.
This approach works, because React guarantees that componentDidMount()
will get called immediately after the component is mounted (aka. written to the DOM). In other words, we know for sure that by the time componentDidMount()
gets called, our component tree has been written to the DOM at least once, and consequently, the top-render
div exists in the DOM.
Upvotes: 4
Reputation: 379
ReactDom.createPortal
is suggested to be used on DOM
nodes outside the scope of React App. Inside you should declare a component normally <Top><MyComponent/></Top>
.
But for curiosity's sake, in case you want to go with this approach only, you should use the ref of the DOM node in Top
element.
ReactDOM.createPortal(
<MyComponent/>,
/*Ref of Container element*/,
);
Upvotes: 0
Reputation: 1546
What do you mean by "Anytime it is imported" ?
Anyway you can just use conditional rendering. It will do the job, but it seems your intention is something else!
render(){
const { showMyComp } = this.state
return(
<App>
<Body>
<Top>
{showMyComp && (<MyComponent />)}
</Top>
<Middle>
<MyComponent/>
</Middle>
</Body>
</App>
)
}
Upvotes: 0