Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use React Portal to render a child into a DOM node of the parent component

What I am attempting to do might seem a little unorthodox so please bear with me.

I want to render a child component into a DOM node of the parent component using a React Portal.

You might think: "This is not the purpose of a Portal and you should just render you children where you want them."

And you may be right but this is a simplified example of an experimental approach I am attempting after trying many other ways.

Anyways, here is the code: (sandbox here)

const Child = ({ color }) => {
  let jsx = <h1>Wow! I am {color}!</h1>;
  let node = document.getElementById(color);
  return ReactDOM.createPortal(jsx, node);
};

class Parent extends React.Component {
  state = {
    color: "green"
  };
  changeColor = () => {
    let color = this.state.color === "green" ? "blue" : "green";
    this.setState({ color });
  };
  render() {
    return (
      <div className="App">
        <Child color={this.state.color} />
        <button onClick={this.changeColor}>change color</button>
        <div className="green" id="green">
          I render green stuff:
        </div>
        <div className="blue" id="blue">
          I render blue stuff:
        </div>
      </div>
    );
  };
};

The problem is that the Parent component's "blue" and "green" divs are not yet rendered when the Child tries to render some jsx into them. So I get the error Target container is not a DOM element. The only way I got it to work is have placing those divs into the html file at the root level.

Any advice for how to do what I am trying with Portals? Please don't say to just do this:

<div className="App">
  <button onClick={this.changeColor}>change color</button>
  <div className="green" id="green">
    I render green stuff:
    {this.state.color === 'green' && <Child color='green' />}
  </div>
  <div className="blue" id="blue">
    I render blue stuff:
    {this.state.color === 'blue' && <Child color='blue' />}
  </div>
</div>

Thank you in advance :-)

like image 613
Egor Egorov Avatar asked Mar 15 '19 19:03

Egor Egorov


1 Answers

Yeah, so your issue is that you can't (at that point) look up the divs by their id as they are rendered by the parent component and they just aren't in the DOM as you'd think.

However! You can just use refs to save the day! Here is an example: https://codesandbox.io/s/r78mplox64?fontsize=14

I replaced the class for functional components because the hooks might make it clearer. Basically, it all it does it pass two refs to the color divs and after render, the current value of the refs would point the node. The useEffect on mount sets the state of colorNode to the current value of the blue ref (which is the <div id="blue"> node).

Then, the button click just swaps out the color variable and color node. Both are then passed in as props and the colorNode prop is passed as the node for the portal to render to.

like image 75
Tom Finney Avatar answered Oct 22 '22 08:10

Tom Finney