0

I am trying to call a function in my child component from a button click event in my parent component.

Parent Component:

class Parent extends Component{
    constructor(props){
        super(props);
        this.state = {
            //..
        }
    }

    handleSaveDialog = (handleSaveClick) => {
        this.handleSaveClick = handleSaveClick;
    }

    render(){
        return(
            <div>
                <Button onClick={this.openDialog}>Open Dialog</Button>
                <Dialog>
                    <DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
                    <DialogContent>
                        <Child handleSaveData={this.handleSaveDialog}/>
                    </DialogContent>
                    <DialogActions>
                        <Button onClick={this.handleSaveClick} color="primary">
                            Save
                        </Button>
                    </DialogActions>
                </Dialog>       
            </div>  
        );
    }
}

In the above code, the Parent component renders a Child component modal dialog (based on Material-UI) on click of a button. The Save button, part of the Dialog component in Parent, when clicked should call a save function in the Child component. As you can see, I have passed a callback function handleSaveDialog through the Childcomponent props named handleSaveData. The save button click will call handleSaveClick on the child, once the Child component mounts and passes the callback to the Parent component.

Child Component:

class Child extends Component{
    constructor(props){
        super(props);
        this.state = {
            //..
        }
    }

    componentDidMount(){
        console.log('mount');
        this.props.handleSaveData( () => this.handleSaveClick());
    }

    handleSaveClick = () => {
        console.log('save clicked');
    }

    render(){
        return(
            <div>
                //..
            </div>      

        );
    }
}

In the above code, I am using the accessing the callback function passed by the Parent component props and binding it to the Child component's save fucntion handleSaveClick.

Problem:

When I click the Open Dialog button in Parent, for the first time, the Dialog mounts the Child component. However, the click on Save button does not work (no error). After, closing the dialog, when I reopen the Dialog and click on Save, the handleSaveClick in the Child dialog is triggered and a message is logged in the browser console. Any idea why this works on the second time and not the first time? Remember, the Child Component is mounted/loaded only when I click the Open Dialog on the Parent component.

References:

https://material-ui.com/components/dialogs/#form-dialogs

Call child method from parent

https://github.com/kriasoft/react-starter-kit/issues/909#issuecomment-390556015

Souvik Ghosh
  • 4,456
  • 13
  • 56
  • 78

2 Answers2

5

It will not work because if you console log this.handleSaveClick in render function it will be undefined as there is no re-render. So there are 2 ways to go for this:

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false
    };
  }

  openDialog = () => {
    this.setState(preState => ({
      open: !preState.open
    }));
  };

  handleSaveDialog = handleSaveRef => {
    this.setState({
      handleSaveClick: handleSaveRef
    });
  };

  render() {
    console.log("Render", this.handleSaveClick);
    return (
      <div>
        <Button onClick={this.openDialog}>Open Dialog</Button>
        <Dialog open={this.state.open}>
          <DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
          <DialogContent>
            <Child handleSaveData={this.handleSaveDialog} />
          </DialogContent>
          <DialogActions>
            <Button onClick={this.state.handleSaveClick} color="primary">
              Save
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
class Child extends Component {

  componentDidMount() {
    console.log("mount");
    this.props.handleSaveData(this.handleSaveClick);
  }

  handleSaveClick = () => {
    console.log("save clicked");
  };

  render() {
    return <div>//..</div>;
  }
}

const childRef = React.createRef();
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false
    };
  }

  openDialog = () => {
    this.setState(preState => ({
      open: !preState.open
    }));
  };

  handleSaveClick = () => {
    if (childRef.current) {
      childRef.current.handleSaveClick();
    }
  };

  render() {
    return (
      <div>
        <Button onClick={this.openDialog}>Open Dialog</Button>
        <Dialog open={this.state.open}>
          <DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
          <DialogContent>
            <Child ref={childRef} />
          </DialogContent>
          <DialogActions>
            <Button onClick={this.handleSaveClick} color="primary">
              Save
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}
class Child extends Component {
  handleSaveClick = () => {
    console.log("save clicked");
  };

  render() {
    return <div>//..</div>;
  }
}

  • Using callback to save instance and than use arrow function:
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false
    };
  }

  openDialog = () => {
    this.setState(preState => ({
      open: !preState.open
    }));
  };

  handleSaveDialog = handleSaveRef => {
    this.handleSaveClick = handleSaveRef;
  };

  render() {
    return (
      <div>
        <Button onClick={this.openDialog}>Open Dialog</Button>
        <Dialog open={this.state.open}>
          <DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
          <DialogContent>
            <Child handleSaveData={this.handleSaveDialog} />
          </DialogContent>
          <DialogActions>
            <Button onClick={() => this.handleSaveClick()} color="primary">
              Save
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}
class Child extends Component {
  componentDidMount() {
    console.log("mount");
    this.props.handleSaveData(this.handleSaveClick);
  }

  handleSaveClick = () => {
    console.log("save clicked");
  };

  render() {
    return <div>//..</div>;
  }
}

You will need to use arrow function in onClick as it will create a new function every time we click and thus getting a new instance of handleClick. And if you pass this.handleClick it won't work because it is undefined. You can check this by logging the value of this.handleClick in render function.


Note: Use the 2 option its more reliable.

Hope this helps!

Yash Joshi
  • 2,586
  • 1
  • 9
  • 18
2

Well, I don't know why you have this sort of scenario but the first things that comes in my mind is that you can write handleSaveClick method in your Parent component. And in case you need to use that function on any action that might be happening in your child component, you can pass this function as a prop from parent component. So your both cases can be handled

  • You can call this method on something happening in parent component
  • You can use the same method on any action that is happening in your child component.

If still you think that you have to define the method in your child component, you can use the refs

Abdul Mannan
  • 170
  • 1
  • 8