0
votes

I have a Material UI Autocomplete which renders a list of Chips. I have added a button at the bottom of the PopperComponent which when clicked should open a dialog box.

But the Autocomplete doesn't allow the DialogBox to be opened. But the weird part is if I add 'open' to Autocomplete it works.

I have tried adding the onMouseDown event instead of onClick. Also, tried the event.preventDefault(). None of them works. However onMouseDown definitely called my listener for the Dialog box and changed its open state to true, but the dialog box did not appear.

This is the link to the sandbox. Sandbox to the code

This is the component that implements the Dialog Box.

import React, { useState } from "react";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import MuiDialogTitle from "@material-ui/core/DialogTitle";
import MuiDialogContent from "@material-ui/core/DialogContent";
import MuiDialogActions from "@material-ui/core/DialogActions";
import IconButton from "@material-ui/core/IconButton";
import CloseIcon from "@material-ui/icons/Close";
import Typography from "@material-ui/core/Typography";
import { orange } from "@material-ui/core/colors";

const styles = theme => ({
  form: {
    display: "flex",
    flexDirection: "column",
    margin: "auto",
    width: "fit-content"
  },
  formControl: {
    marginTop: theme.spacing(2),
    minWidth: 120
  },
  formControlLabel: {
    marginTop: theme.spacing(1)
  },
  closeButton: {
    position: "absolute",
    right: theme.spacing(1),
    top: theme.spacing(1),
    color: theme.palette.grey[500]
  },
  selectEmpty: {
    marginTop: theme.spacing(2)
  },
  floatingLabelFocusStyle: {
    color: "green"
  },
  separator: {
    marginTop: theme.spacing(1)
  },
  menuStyle: {
    border: "1px solid black",
    borderRadius: "5%",
    backgroundColor: "lightgrey"
  }
});

const DialogTitle = withStyles(styles)(props => {
  const { children, classes, onClose, ...other } = props;
  return (
    <MuiDialogTitle disableTypography className={classes.root} {...other}>
      <Typography variant="h6">{children}</Typography>
      {onClose ? (
        <IconButton
          aria-label="close"
          className={classes.closeButton}
          onClick={onClose}
        >
          <CloseIcon />
        </IconButton>
      ) : null}{" "}
    </MuiDialogTitle>
  );
});

const DialogContent = withStyles(theme => ({
  root: {
    padding: theme.spacing(2)
  }
}))(MuiDialogContent);

const DialogActions = withStyles(theme => ({
  root: {
    margin: 0,
    padding: theme.spacing(1)
  }
}))(MuiDialogActions);

const ActionButton = withStyles(theme => ({
  root: {
    color: "#E87424",
    backgroundColor: "white",
    "&:hover": {
      backgroundColor: orange[100]
    }
  }
}))(Button);

const ManageTagButton = withStyles(theme => ({
  root: {
    color: "#E87424"
  }
}))(Button);

const TagContainer = props => {
  const [open, setOpen] = useState(false);

  const handleClickOpen = () => {
    console.log("Dialog box clicked");
    setOpen(true);
  };
  const handleClose = () => {
    setOpen(false);
  };

  return (
    <div>
      <ManageTagButton
        onMouseDown={event => {
          event.preventDefault();
          handleClickOpen();
        }}
        size="small"
      >
        MANAGE TAGS
      </ManageTagButton>

      <Dialog
        fullWidth
        maxWidth={"sm"}
        onClose={handleClose}
        aria-labelledby="customized-dialog-title"
        open={open}
      >
        <DialogTitle id="customized-dialog-title">Manage Tags</DialogTitle>
        <DialogContent dividers>
          <h1>
            This is the component of the dialog box. In reality I neeed to
            display a data table with CRUD operations to add more tags.
          </h1>
        </DialogContent>
        <DialogActions>
          <ActionButton autoFocus onClick={handleClose} color="secondary">
            CLOSE
          </ActionButton>
        </DialogActions>
      </Dialog>
    </div>
  );
};

export default TagContainer;

This is the component that implements the Autocomplete.

import React, { Fragment } from "react";
import Chip from "@material-ui/core/Chip";
import Autocomplete from "@material-ui/lab/Autocomplete";
import { withStyles } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import Paper from "@material-ui/core/Paper";
import TagContainer from "./TagContainer";

const ListItemCustom = withStyles(theme => ({
  gutters: {
    paddingLeft: 0,
    paddingRight: 0
  },
  secondaryAction: {
    paddingRight: 0
  }
}))(ListItem);

const AutocompleteCustom = withStyles(theme => ({
  endAdornment: {
    display: "none"
  }
}))(Autocomplete);

const CreateButton = withStyles(theme => ({
  root: {
    color: "#E87424"
  }
}))(Button);

const MuiFilledInputCustom = makeStyles(
  {
    underline: {
      "&&&:before": {
        borderBottom: "none"
      },
      "&&:after": {
        borderBottom: "none"
      }
    }
  },
  { name: "MuiFilledInput" }
);

const loadCustomStyles = () => {
  MuiFilledInputCustom();
};

export default function AddTagToThread() {
  loadCustomStyles();

  const handleSubmit = () => {
    console.log("Add tags to thread");
  };

  const useStyles = makeStyles({
    root: {
      minWidth: 300,
      width: 300,
      height: 250,
      minHeight: 250,
      zIndex: 1
    },
    buttons: {
      display: "flex",
      justifyContent: "flex-end"
    }
  });
  const PaperComponentCustom = options => {
    const classes = useStyles();
    const { containerProps, children } = options;

    return (
      <Paper className={classes.root} {...containerProps} square>
        {children}
        <div className={classes.buttons}>
          <TagContainer />
        </div>
      </Paper>
    );
  };

  return (
    <List dense={false}>
      <ListItemCustom>
        <ListItemText>
          <AutocompleteCustom
            multiple
            id="size-small-filled-multi"
            size="medium"
            options={tagList}
            noOptionsText="No options"
            freeSolo
            filterSelectedOptions
            PaperComponent={PaperComponentCustom}
            getOptionLabel={option => option.name}
            onChange={(event, value) => {
              console.log(value);
            }}
            renderTags={(value, getTagProps) =>
              value.map((option, index) => (
                <Chip
                  variant="default"
                  style={{
                    backgroundColor: option.color
                  }}
                  label={option.name}
                  size="medium"
                  {...getTagProps({ index })}
                />
              ))
            }
            renderOption={option => (
              <Fragment>
                <Chip
                  variant="default"
                  style={{
                    backgroundColor: option.color,
                    padding: "15px",
                    marginLeft: "12px"
                  }}
                  label={option.name}
                  size="medium"
                />
              </Fragment>
            )}
            renderInput={params => (
              <TextField
                {...params}
                variant="filled"
                label="Filter By Tag"
                placeholder="Select Tag"
              />
            )}
          />
        </ListItemText>
        <ListItemSecondaryAction>
          <CreateButton onClick={handleSubmit}>ADD TAG</CreateButton>
        </ListItemSecondaryAction>
      </ListItemCustom>
    </List>
  );
}

const tagList = [
  { name: "Follow Up", tagId: 1, color: "#FFC107" },
  { name: "Important", tagId: 2, color: "#46B978" },
  { name: "Idea", tagId: 3, color: "#EEA5F6" },
  { name: "Non Issue", tagId: 4, color: "#2EACE2" }
];

I have been stuck at this for last couple of days. Any help is greatly appreciated.

1
I think that the issue here is that the Paper Component is unmounted after the click so there is actually no dialog to show. Also I don't get the following part But the weird part is if I add 'open' to Autocomplete it works. can you detail pleaseSabbin
Yes, I think you're right, the popper is unmounted before the dialogbox opens. But, if I keep my AutoComplete popper open by adding the 'open' prop, then it works cause it cases the paper component to stay mounted.Sabyasachi
How do I keep the paper component from getting unmounted when the dialog box opens?Sabyasachi
You don't, you keep only the button there and the dialog component outside the autocomplete. If you want I can modify your sandbox and show you howSabbin
Sure, Please show me how to do it. Thank youSabyasachi

1 Answers

1
votes

The issue with your code is that the <Dialog/> component is in the PaperComponentCustom component which gets unmounted after the option is selected.

<Paper className={classes.root} {...containerProps} square>
    {children}
    <ManageTagButton onMouseDown={handleClickOpen} fullWidth>
       MANAGE TAGS
    </ManageTagButton>
</Paper>

The solution to keep only the <ManageTagButton/> component in the PaperComponentCustom and move the <Dialog/> component one level up. I imagine that even if you have 10 elements in the <List/> you would still have only one <Dialog>, you cannot have 10 dialog components opened at once.

So therefore your <AddTagToThread/> component should render the dialog directly and the state of the dialog open and the handlers handleOpen and handleClose should be moved in the <AddTagToThread/> component also

Working codesandbox HERE, code below

Autocomplete component

import React, { Fragment, useState } from "react";
import Chip from "@material-ui/core/Chip";
import Autocomplete from "@material-ui/lab/Autocomplete";
import { withStyles } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import Paper from "@material-ui/core/Paper";
import TagContainer from "./TagContainer";

const ListItemCustom = withStyles(theme => ({
  gutters: {
    paddingLeft: 0,
    paddingRight: 0
  },
  secondaryAction: {
    paddingRight: 0
  }
}))(ListItem);

const AutocompleteCustom = withStyles(theme => ({
  endAdornment: {
    display: "none"
  }
}))(Autocomplete);

const CreateButton = withStyles(theme => ({
  root: {
    color: "#E87424"
  }
}))(Button);

const MuiFilledInputCustom = makeStyles(
  {
    underline: {
      "&&&:before": {
        borderBottom: "none"
      },
      "&&:after": {
        borderBottom: "none"
      }
    }
  },
  { name: "MuiFilledInput" }
);

const loadCustomStyles = () => {
  MuiFilledInputCustom();
};

const ManageTagButton = withStyles(theme => ({
  root: {
    color: "#E87424"
  }
}))(Button);

export default function AddTagToThread() {
  loadCustomStyles();

  const handleSubmit = () => {
    console.log("Add tags to thread");
  };

  const [open, setOpen] = useState(false);

  const handleClickOpen = () => {
    console.log("Dialog box clicked");
    setOpen(true);
  };
  const handleClose = () => {
    setOpen(false);
  };

  const useStyles = makeStyles({
    root: {
      minWidth: 300,
      width: 300,
      height: 250,
      minHeight: 250,
      zIndex: 1
    },
    buttons: {
      display: "flex",
      justifyContent: "flex-end"
    }
  });
  const PaperComponentCustom = options => {
    const classes = useStyles();
    const { containerProps, children } = options;

    return (
      <Paper className={classes.root} {...containerProps} square>
        {children}
        <ManageTagButton onMouseDown={handleClickOpen} fullWidth>
          MANAGE TAGS
        </ManageTagButton>
      </Paper>
    );
  };

  return (
    <>
      <TagContainer open={open} handleClose={handleClose} />
      <List dense={false}>
        <ListItemCustom>
          <ListItemText>
            <AutocompleteCustom
              multiple
              id="size-small-filled-multi"
              size="medium"
              options={tagList}
              noOptionsText="No options"
              freeSolo
              filterSelectedOptions
              PaperComponent={PaperComponentCustom}
              getOptionLabel={option => option.name}
              onChange={(event, value) => {
                console.log(value);
              }}
              renderTags={(value, getTagProps) =>
                value.map((option, index) => (
                  <Chip
                    variant="default"
                    style={{
                      backgroundColor: option.color
                    }}
                    label={option.name}
                    size="medium"
                    {...getTagProps({ index })}
                  />
                ))
              }
              renderOption={option => (
                <Fragment>
                  <Chip
                    variant="default"
                    style={{
                      backgroundColor: option.color,
                      padding: "15px",
                      marginLeft: "12px"
                    }}
                    label={option.name}
                    size="medium"
                  />
                </Fragment>
              )}
              renderInput={params => (
                <TextField
                  {...params}
                  variant="filled"
                  label="Filter By Tag"
                  placeholder="Select Tag"
                />
              )}
            />
          </ListItemText>
          <ListItemSecondaryAction>
            <CreateButton onClick={handleSubmit}>ADD TAG</CreateButton>
          </ListItemSecondaryAction>
        </ListItemCustom>
      </List>
    </>
  );
}

const tagList = [
  { name: "Follow Up", tagId: 1, color: "#FFC107" },
  { name: "Important", tagId: 2, color: "#46B978" },
  { name: "Idea", tagId: 3, color: "#EEA5F6" },
  { name: "Non Issue", tagId: 4, color: "#2EACE2" }
];

Dialog component

import React, { useState } from "react";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import MuiDialogTitle from "@material-ui/core/DialogTitle";
import MuiDialogContent from "@material-ui/core/DialogContent";
import MuiDialogActions from "@material-ui/core/DialogActions";
import IconButton from "@material-ui/core/IconButton";
import CloseIcon from "@material-ui/icons/Close";
import Typography from "@material-ui/core/Typography";
import { orange } from "@material-ui/core/colors";

const styles = theme => ({
  form: {
    display: "flex",
    flexDirection: "column",
    margin: "auto",
    width: "fit-content"
  },
  formControl: {
    marginTop: theme.spacing(2),
    minWidth: 120
  },
  formControlLabel: {
    marginTop: theme.spacing(1)
  },
  closeButton: {
    position: "absolute",
    right: theme.spacing(1),
    top: theme.spacing(1),
    color: theme.palette.grey[500]
  },
  selectEmpty: {
    marginTop: theme.spacing(2)
  },
  floatingLabelFocusStyle: {
    color: "green"
  },
  separator: {
    marginTop: theme.spacing(1)
  },
  menuStyle: {
    border: "1px solid black",
    borderRadius: "5%",
    backgroundColor: "lightgrey"
  }
});

const DialogTitle = withStyles(styles)(props => {
  const { children, classes, onClose, ...other } = props;
  return (
    <MuiDialogTitle disableTypography className={classes.root} {...other}>
      <Typography variant="h6">{children}</Typography>
      {onClose ? (
        <IconButton
          aria-label="close"
          className={classes.closeButton}
          onClick={onClose}
        >
          <CloseIcon />
        </IconButton>
      ) : null}{" "}
    </MuiDialogTitle>
  );
});

const DialogContent = withStyles(theme => ({
  root: {
    padding: theme.spacing(2)
  }
}))(MuiDialogContent);

const DialogActions = withStyles(theme => ({
  root: {
    margin: 0,
    padding: theme.spacing(1)
  }
}))(MuiDialogActions);

const ActionButton = withStyles(theme => ({
  root: {
    color: "#E87424",
    backgroundColor: "white",
    "&:hover": {
      backgroundColor: orange[100]
    }
  }
}))(Button);

const TagContainer = ({ open, handleClose }) => {
  return (
    <Dialog
      fullWidth
      maxWidth={"sm"}
      onClose={handleClose}
      aria-labelledby="customized-dialog-title"
      open={open}
    >
      <DialogTitle id="customized-dialog-title">Manage Tags</DialogTitle>
      <DialogContent dividers>
        <h1>
          This is the component of the dialog box. In reality I neeed to display
          a data table with CRUD operations to add more tags.
        </h1>
      </DialogContent>
      <DialogActions>
        <ActionButton autoFocus onClick={handleClose} color="secondary">
          CLOSE
        </ActionButton>
      </DialogActions>
    </Dialog>
  );
};

export default TagContainer;