3

I have a component called Parent, inside of which there is another component named Child:

<Parent>
  <Child/>
</Parent>

So the lifecycle is as follows:

  1. Parent constructor
  2. Parent's render()
  3. Child constructor
  4. Child's render()
  5. Child is mounted
  6. Parent is mounted

Can I do additional Parent initialisation after step 2 and before step 3 somehow?

UPDATE:

class ThirdPartyLib {
  init(elementId) {
    console.log(`initializing element: ${elementId}`);
    // element with #id: elementId should exist!
    // document.getElementById(elementId).style.color = "red";
  }
}

class Parent extends React.Component {
    constructor(props) {
        super(props);
        console.log("Parent's constructor");
    }

    render() {
        console.log("rendering Parent");
        new ThirdPartyLib().init("parent");
        return (
            <div id="parent">Parent: {this.props.name}
                <Child name="Sara"/>
            </div>
        );
    }

    componentDidMount() {
        console.log("Parent is mounted");
    }
}

class Child extends React.Component {
    constructor(props) {
        super(props);
        console.log(`Child ${this.props.name} constructor`);
    }

    render() {
        console.log(`rendering Child: ${this.props.name}`);
        return <div>Child: {this.props.name}</div>
    }

    componentDidMount() {
        console.log(`Child ${this.props.name} is mounted`);
    }
}

ReactDOM.render(<Parent name="Bob"/>, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id="app"></div>

Here I made some simplifications - I am not merely changing the element's color, I could have made it in the componentDidMount() method. Rather, the ThirdPartyLib specifics dictate the initialization sequence. I have to initialize the Parent right after it appears in the DOM, before any child element is created.

To be more specific, Parent and Child share the exact same instance of the ThirdPartyLib class. I cannot put the initialization logic into Parent's render() function, since the element is not in the DOM yet. Likewise, I cannot initialize the Parent before the Child as suggested in the comments via componentDidMount(), since the Child's componentDidMount() is executed before the Parent's.

maslick
  • 2,903
  • 3
  • 28
  • 50
  • 5
    What kind of initialization? What function call? Please, be more specific and explain your case. The code can be placed into Parent's `render` but it depends on the case whether this is appropriate. This can be XY problem. – Estus Flask Jan 16 '19 at 14:41
  • the parent's render cannot complete before the child's renders are completed since the parent markup is composed of the child's markup. I don't see how this would be possible. – apokryfos Jan 16 '19 at 14:46
  • @estus I'm using a 3rd party library, which needs an element's #id as input parameter. The html element is created by the Parent's ``render()`` method. I cannot wait until Parent's ``componentDidMount()`` is executed, because it's too late. I cannot put the initialisation in the ``render()`` function either, simply because the element is not there yet (in the DOM). – maslick Jan 16 '19 at 17:53
  • You have XY problem. The thing you describe should happen in componentDidMount. Whatever the problem is, it should be addressed from that side. Please, update the question with specific code. The solution may be specific to a library you use. See https://stackoverflow.com/help/mcve . – Estus Flask Jan 16 '19 at 18:04
  • @estus see the updated question. – maslick Jan 16 '19 at 18:59

1 Answers1

6

One way to approach this is to delay rendering the child until after mount of the parent. The steps would look as follows:

  • initial Parent render doesn't render Child (e.g. suppress using a flag in Parent state)
  • Parent componentDidMount performs third-party initialization and changes flag in Parent state triggering a re-render of the parent
  • re-render of Parent will now render the Child and the Parent can pass third-party initialization info to Child via a prop

The resulting code would be something like:

import React from "react";
import ReactDOM from "react-dom";

class ThirdPartyLib {
  init(elementId) {
    console.log(`initializing element: ${elementId}`);
    this.element = document.getElementById(elementId);
    this.element.style.color = "red";
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { initialized: false };
    console.log("Parent's constructor");
  }

  render() {
    console.log("rendering Parent");
    return (
      <div id="parent">
        Parent: {this.props.name}
        {this.state.initialized && (
          <Child name="Sara" thirdPartyLib={this.state.thirdPartyLib} />
        )}
      </div>
    );
  }

  componentDidMount() {
    console.log("Parent is mounted");
    const thirdPartyLib = new ThirdPartyLib();
    thirdPartyLib.init("parent");
    this.setState({ initialized: true, thirdPartyLib });
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props);
    console.log(`Child ${this.props.name} constructor`);
    console.log(
      `Child knows stuff from thirdPartyLib: ${
        this.props.thirdPartyLib.element.id
      }`
    );
  }

  render() {
    console.log(`rendering Child: ${this.props.name}`);
    return (
      <div>
        Child: {this.props.name}
        <br />
        ThirdPartyLib element id:
        {this.props.thirdPartyLib.element.id}
      </div>
    );
  }

  componentDidMount() {
    console.log(`Child ${this.props.name} is mounted`);
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Parent name="Bob" />, rootElement);

Edit 82q69v3469

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198