maslick
maslick

Reputation: 3360

React: Function call after render() and before child's constructor()

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.

Upvotes: 3

Views: 6360

Answers (1)

Ryan Cogswell
Ryan Cogswell

Reputation: 81006

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

Upvotes: 6

Related Questions