2

I have a react modal, which renders it's children:

// base_modal.js  
import React, { Component } from 'react';
import Modal from 'react-modal';

export default class BaseModal extends Component {
  componentDidMount() {
    console.log("BaseModal mounted");
  }

  render() {
    return (
      <Modal
        isOpen={this.props.showModal}
        className="base-modal"
        overlayClassName="base-modal-overlay"
        onRequestClose={this.props.onRequestClose}
        contentLabel="base-modal"
      >
        {this.props.children}
      </Modal>
    );
  }
}

I have another component that renders BaseModal passes in some children:

// my_modal.js
import BaseModal from './base_modal'

export default class MyModal extends Component {
  constructor(props) {
    super(props);
    this.doSomeSetup = this.doSomeSetup.bind(this);
  }

  componentDidMount() {
    console.log("MyModal mounted");
    this.doSomeSetup();
  }

  doSomeSetup() {
    console.log(this.dateInput); // undefined
  }

  render() {
    return (
       <BaseModal
        showModal={this.props.showModal}
        onRequestClose={this.props.onClose}
       >
        <form>
          <div>
            <input
              ref={ input => { console.log("In the input ref"); this.dateInput = input; }}
              type='text'
              value={this.state.inputVal}
              onChange={this.handleChange}
            />
          </div>
        </form>
      </BaseModal>
    )
  }
}

I have a component that renders MyModal:

// my_view.js
import MyModal from './my_modal'

export default class MyView extends Component {
  constructor(props) {
    super(props)
    this.state = {
      showModal: false
    }
    this.showModal = this.showModal.bind(this);
    this.hideModal = this.hideModal.bind(this);
  }

  showModal() {
    this.setState({
      showModal: true
    });
  }

  hideModal() {
    this.setState({
      showModal: false
    });
  }

  render() {
    return (
      <div>
        <Button onClick={this.showModal} />
        <MyModal 
          showModal={false}
          onClose={this.hideModal}
        />
      </div>
    )
  }
}

The problem is that in MyModal, this.dateInput is not defined when the component mounts. Whenever I render MyView, it renders MyModal, but MyModal renders BaseModal which only renders it's children when it has the prop "isOpen" set to true.

When I render MyView, I see the console logs "BaseModal mounted", "MyModal mounted", and then "undefined" for the console.log in the doSomeSetup method (i.e., the ref callback hasn't been executed). When I then click on the button in MyView to open the modal, I see the console.log "In the input ref". So the setup that I want to happen with the input in MyModal is not happening, because the actual form input's ref is only being executed when the React Modal has the prop isOpen set to true.

Another setup is with making the following changes:

// my_modal.js

componentDidMount() {
  console.log("MyModal mounted");
}

doSomeSetup(input) {
  console.log(input);
}


(...) ref={ input => { console.log("In the input ref"); this.doSomeSetup(input); }}

However, now, there is the following behaviour. When I render MyView, I see "BaseModal mounted", "MyModal mounted". When I click on the Button to open the modal I see "In the input ref" and the the actual input element gets console.logged.

Here is the weird thing (although as I'm writing this I'm beginning to see the answer to my own doubts): let's say I add a button to the render method of MyModal, that just changes the state of inputVal, forcing a rerender:

  changeState() {
    this.setState({
      inputVal: this.state.inputVal + 1
    });
  }

  render() {
    return (
       <BaseModal
        showModal={this.props.showModal}
        onRequestClose={this.props.onClose}
       >
        <button onClick={this.changeState}
        <form>
          <div>
            <input
              ref={ input => { console.log("In the input ref"); this.doSomeSetup(input); }}
              type='text'
              value={this.state.inputVal}
              onChange={this.handleChange}
            />
          </div>
        </form>
      </BaseModal>
    )
  }

Now what happens when I click on the button is "In the input ref", then the console.log from doSomeSetup is executed and produces "null", then I get another "In the input ref", and then the console.log from doSomeSetup produces an actual input element. So Actually it seems like the children that are passed into BaseModal (my Button and Form) are first unmounted, and then mounted again (I think this because ref callbacks get called with null as argument when the node will unmount). Which is weird.

So Now, I guess my question is: Does the above make sense, and is it normal that the parent component unmounts and mounts its children everytime? Is this even the right way to describe what is happening?

To be more precise the functionality that I want is to add an event handler to the input in the doSomeSetup. But it seems that anytime the input were to change, because it is contained in a child of BaseModal, which is a prop to BaseModal, the latter somehow decides to unmount and mount the children. Why doesn't it just rerender the children? Is there a better way to do this?

evianpring
  • 3,316
  • 1
  • 25
  • 54

0 Answers0