Pablo
Pablo

Reputation: 29519

How to handle dialog state outside of dialog component?

I have the following dialog component:

class LoginDialog extends React.Component {
  state = {
    open: false,
  };

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

  handleClose = () => {
    this.setState({ open: false });
  };

  render() {
    return (
      <div>
        <Dialog
          open={this.state.open}
          onClose={this.handleClose}
        >
          <DialogActions>
            <Button onClick={this.handleClose} color="primary">
              Cancel
            </Button>
            <Button onClick={this.handleClose} color="primary">
              Subscribe
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}

How can I open that dialog from parent component AND ensure the close dialog also works? This is my attempt

class MainAppBar extends React.Component {
    state = {
    openLoginDialog: false,
    openRegisterDialog: false
    };
    render() {
        return (
            <div>
                <Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
                )}
                <LoginDialog /*not sure how to pass here openLoginDialog*//>
            </div>
        );
    }
}

So I am not sure whether I really have to keep dialog states in both child/parent and how to properly open it from parent.

Upvotes: 2

Views: 3441

Answers (4)

Asaf Aviv
Asaf Aviv

Reputation: 11760

I will take a different approach than the other answers and only include LoginDialog when it's needed.

We can now make LoginDialog a functional component and lift the state up to the Parent component. now our LoginDialog is much simpler and easier to test and doesn't depend on anything

class Parent extends React.Component {
  state = {
    isOpen: false,
  };

  // No need to use open and close handler because if the modal
  // is open another execute of the function will close it
  // this way we can still toggle it from the button that's opening the Dialog
  toggleDialog = () => {
    this.setState(prevState => ({
      open: !prevState.open,
    }));
  };

  // if you want make the handler more flexible you can write it like this
  // make it a toggle by default with an optional nextState to
  // make it more flexible
  dialogStateHandler = (nextState) => () => {
    this.setState(prevState => ({
      open: nextState || !prevState.open,
    }));
  };
  // to use this handler you will need to invoke it and passing
  // in the nextState or without to make it toggle
  // onClick={this.dialogStateHandler(true / false || without args to toggle)}

  render() {
    const { isOpen } = this.state;

    return (
      <div>
        <button onClick={this.toggleDialog}>Toggle</button>
        {/* include the Dialog component only when its open */}
        {isOpen && <LoginDialog closeDialog={this.toggleDialog} />}
      </div>
    );
  }
}

Receive closeDialog as props from Parent and pass it down to Child components

const LoginDialog = ({ closeDialog }) => (
  <div>
    <Dialog
      closeDialog={closeDialog}
    >
      <DialogActions>
        <Button onClick={closeDialog} color="primary">
          Cancel
        </Button>
        <Button onClick={closeDialog} color="primary">
          Subscribe
        </Button>
      </DialogActions>
    </Dialog>
    )}
  </div>
);

Upvotes: 0

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15688

You could define handleClose() or an equivalent an event-handler inside MainAppBar component and pass that down to the child. It can manage the state-variables (true/false) on the Parent and pass that boolean value into LoginDialog bar to determine if they should be open. That way the state of the child will be managed by the parent.

class MainAppBar extends React.Component {
    state = {
    openLoginDialog: false,
    openRegisterDialog: false
    };

    toggleDialog = () => {
       this.setState((prevState) => {
          return{
             openLoginDialog: !prevState.openLoginDialog
          }
       })
    }

    render() {
        return (
            <div>
                <Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
                )}
                <LoginDialog open={this.state.openLoginDialog} toggle={this.toggleDialog}/>
            </div>
        );
    }
}

Then:

class LoginDialog extends React.Component {

  render() {
    return (
      <div>
        <Dialog
          open={this.props.open}
          onClose={() => this.props.toggle} //not sure what this listener does, but im assuming you want to close it
        >
          <DialogActions>
            <Button onClick={() => this.props.toggle} color="primary">
              Cancel
            </Button>
            <Button onClick={() => this.props.toggle} color="primary">
              Subscribe
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}

Upvotes: 0

Avanthika
Avanthika

Reputation: 4182

You have to maintain the state whether the login dialog is open or not in the parent. Pass the open/close status to the child, and the callback to close the dialog to the child via props.

class MainAppBar extends React.Component {
  state = {
    openLoginDialog: false,
    openRegisterDialog: false
  };

  openLoginDialog = () => {
    this.setState({
      openLoginDialog: true
    });
  };

  closeLoginDialog = () => {
    this.setState({
      openLoginDialog: false
    });
  };
  render() {
    return (
      <div>
        <Button color="inherit" onClick={() => this.openLoginDialog()}>
          Login
        </Button>
        )}
        <LoginDialog
          closeLoginDialog={this.closeLoginDialog}
          isLoginDialogOpen={this.state.openLoginDialog}
        />
      </div>
    );
  }
}

This component doesn't need any state management since we're managing it in the parent. We can make is pure this way:

const LoginDialog = props => (
  <div>
    <Dialog open={props.isLoginDialogOpen} onClose={props.closeLoginDialog}>
      <DialogActions>
        <Button onClick={props.closeLoginDialog} color="primary">
          Cancel
        </Button>
        <Button onClick={props.closeLoginDialog} color="primary">
          Subscribe
        </Button>
      </DialogActions>
    </Dialog>
  </div>
);

Hope this is helpful!

Upvotes: 2

GalAbra
GalAbra

Reputation: 5148

If you let the parent component manage the dialog's status, you can allow it full control over it, while passing the control function to the dialog element itself:

class MainAppBar extends React.Component {
        constructor(props) {
            this.state = {
                openLoginDialog: false,
                openRegisterDialog: false
            };
        }

        closeDialog() {  // This method will be passed to the dialog component
            this.setState({
                openLoginDialog: false
            });
        }

        render() {
            return (
                <div>
                    <Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
                    )}
                    <LoginDialog isOpen={this.state.openLoginDialog} closeDialog={this.closeDialog}>
                </div>
            );
        }
    }

class LoginDialog extends React.Component {
  render() {
    return (
      <div>
        <Dialog
          open={this.props.isOpen}
          onClose={this.props.closeDialog}
        >
          <DialogActions>
            <Button onClick={this.props.closeDialog} color="primary">
              Cancel
            </Button>
            <Button onClick={this.props.closeDialog} color="primary">
              Subscribe
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}

Upvotes: 0

Related Questions