I have a component in my react s application where i use ClickAwayListener from #mui/material/ClickAwayListener.
export default function MultipleSelectChip() {
const theme = useTheme();
const [personName, setPersonName] = React.useState([]);
const handleChange = (event) => {
const {
target: { value }
} = event;
setPersonName(
// On autofill we get a the stringified value.
typeof value === "string" ? value.split(",") : value
);
};
return (
<div>
{[1, 2, 3].map(() => {
return (
<div>
<ClickAwayListener onClickAway={() => console.log("click outside")}>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
value={personName}
onChange={handleChange}
input={
<OutlinedInput id="select-multiple-chip" label="Chip" />
}
renderValue={(selected) => (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</ClickAwayListener>
</div>
);
})}
</div>
);
}
I expect to trigger onClickAway function only when i click outside a dropdown. But when i click the dropdown plus when i select an option the function also is triggered and i don't understand why. Hw to get the expected behaviour when click outside one of the dropdown? NOTE: the functionality works if i don't use map(), meaning without many dropdowns. demo: https://codesandbox.io/s/multipleselectchip-material-demo-forked-n03vq?file=/demo.js:1080-2905
Related
I've made a custom filter for MUI's datagrid, the filter has two select's which allow you to filter by the column and filter type. The selects are quite big and endup outside the modal, when clicking on an option the whole modal closes, how can I prevent this from happening?
I've used this tutorial - Detect click outside React component to detect clicks outside the filter.
The code below shows the filter and I've also made an codesandbox example here - https://codesandbox.io/s/awesome-panka-g92vhn?file=/src/DataGridCustomFilter.js:0-6708
any help would be appreciated
import React, { useState, useEffect, useRef } from "react";
import {
Button,
Stack,
FormControl,
InputLabel,
Select,
MenuItem,
Paper,
Grid,
IconButton,
TextField,
ClickAwayListener
} from "#material-ui/core";
import FilterListIcon from "#mui/icons-material/FilterList";
import AddIcon from "#mui/icons-material/Add";
import CloseIcon from "#mui/icons-material/Close";
import { useForm, useFieldArray, Controller } from "react-hook-form";
import { columns } from "./columns";
const filterTypes = {
string: ["contains", "equals", "starts with", "ends with", "is any of"],
int: ["contains", "equals", "less than", "greater than"]
};
function FilterRow({
len,
setOpen,
field,
control,
columns,
index,
handleRemoveFilter
}) {
return (
<Grid container spacing={0}>
<Grid
item
md={1}
style={{
display: "flex",
alignSelf: "flex-end",
alignItems: "center"
}}
>
<IconButton
size="small"
onClick={() => {
if (len === 1) {
setOpen(false);
} else {
console.log(index, "---");
handleRemoveFilter(index);
}
}}
>
<CloseIcon style={{ fontSize: "20px" }} />
</IconButton>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.column`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Column</InputLabel>
<Select
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<MenuItem value={a.headerName}>{a.headerName}</MenuItem>
);
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={3}>
<Controller
name={`filterForm.${index}.filter`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Filter</InputLabel>
<Select
value={value}
onChange={onChange}
label="Filter"
defaultValue=""
>
{filterTypes.string.map((a) => {
return <MenuItem value={a}>{a}</MenuItem>;
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.value`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl>
<TextField
onChange={onChange}
value={value}
label="Value"
variant="standard"
/>
</FormControl>
)}
/>
{/* )} */}
</Grid>
</Grid>
);
}
function DataGridCustomFilter() {
const { control, handleSubmit } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: "filterForm"
});
const [open, setOpen] = useState(false);
const onSubmit = (data) => {};
useEffect(() => {
if (fields.length === 0) {
append({
column: "ID",
filter: filterTypes.string[0],
value: ""
});
}
}, [fields]);
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = (e) => {
if (myRef.current && !myRef.current.contains(e.target)) {
setClickedOutside(true);
setOpen(!open);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
});
return (
<>
<Button
startIcon={<FilterListIcon />}
size="small"
onClick={() => {
setOpen(!open);
}}
// disabled={isDisabled}
>
FILTERS
</Button>
{open ? (
<div ref={myRef}>
<Paper
style={{
width: 550,
padding: 10,
zIndex: 1300,
position: "absolute",
inset: "0px auto auto 0px",
margin: 0,
display: "block"
// transform: "translate3d(160.556px, 252.222px, 0px)",
}}
variant="elevation"
elevation={5}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={0.5}>
<div style={{ maxHeight: 210, overflow: "scroll" }}>
{fields.map((field, index) => {
return (
<div style={{ paddingBottom: 5 }}>
<FilterRow
len={fields.length}
control={control}
setOpen={setOpen}
field={field}
columns={columns}
handleRemoveFilter={() => remove(index)}
{...{ control, index, field }}
// handleClickAway={handleClickAway}
/>
</div>
);
})}
</div>
<div style={{ marginTop: 10, paddingLeft: 40 }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<AddIcon />}>
ADD FILTER
</Button>
<Button size="small" type="submit">
{fields.length > 1 ? "APPLY FILTERS" : "APPLY FILTER"}
</Button>
</Stack>
</div>
</Stack>
</form>
</Paper>
</div>
) : null}
</>
);
}
export default DataGridCustomFilter;
So far I've tried MUI's ClickAwayListener and the example above, both seem to give the same result
DataGrid component uses NativeSelect. I have checked your codesandbox and tried replacing Select to NativeSelect and MenuItem to Option. filter is working properly. below is sample code for update.
...
<NativeSelect
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<option value={a.headerName}>{a.headerName}</option >
);
})}
</NativeSelect>
...
I'm a newbie in React and Typescript and I'm trying to render some dropdowns from a dialog combining the Select(Chip) and Dialog from Material UI.
The dropdowns are displayed well when I open them, but When I click on any of the displayed values, this overwrite the rest of the dropdowns.
Any help with these?
Thanks you all!
[1]: https://i.stack.imgur.com/QkeZQ.png
[2]: https://i.stack.imgur.com/P45sc.png
[3]: https://i.stack.imgur.com/OWp7D.png
export default function DialogSelect(props: { data: Map<string, any[]>; colName: string; }) {
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const [attributeName, setattributeName] = React.useState<string[]>([]);
const handleChangeChip = (event: SelectChangeEvent<typeof attributeName>) => {
const {
target: { value },
} = event;
setattributeName(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value,
);
};
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = (event: React.SyntheticEvent<unknown>, reason?: string) => {
if (reason !== 'backdropClick') {
setOpen(false);
}
};
const handleDelete = (e: React.MouseEvent, value: string) => {
e.preventDefault();
console.log("clicked delete");
setattributeName((current) => _without(current, value));
};
return (
<div>
<Button onClick={handleClickOpen}>Open select dialog</Button>
<Dialog disableEscapeKeyDown open={open} onClose={handleClose}>
<DialogTitle>Fill the form</DialogTitle>
<DialogContent>
<Box component="form" sx={{ display: 'flex', flexWrap: 'wrap' }}>
{Array.from(props.data).map((attribute, index) => (
<FormControl sx={{ m: 1, width: 300}}>
<InputLabel id={"demo-multiple-chip-label"+attribute[0]}>{attribute[0]}</InputLabel>
<Select sx={{backgroundColor:'honeydew'}}
labelId={"demo-multiple-chip-label"+attribute[0]}
id={"demo-multiple-chip"+attribute[0]}
className="attributeValues"
multiple
value={attributeName}
onChange={handleChangeChip}
input={<OutlinedInput id="select-multiple-chip" label={props.colName} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip clickable variant="outlined" deleteIcon={
<CancelIcon
onMouseDown={(event) => event.stopPropagation()}
/>
} onDelete={(e) => handleDelete(e, value)} key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{attribute[1].map((name: any) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, attributeName, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
))}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose}>Ok</Button>
</DialogActions>
</Dialog>
</div>
);
}
I have a filter component that renders a several checkboxes and a parent component that controls the state. Everything renders fine but the checkboxes are not clickable and are not firing any event.
Here the filter component:
const FilterComponent = ({ options, handleChange, checked }) => {
return (
<Box sx={{ backgroundColor: '#EDEDED', borderRadius: '5px' }}>
<FormControl sx={{ padding: '19px' }}>
<FormGroup>
<Typography component='h2' variant='h6' sx={{ fontWeight: '700' }}>Brand</Typography>
{options['brands'].map((brand, index) => {
return (
<FormControlLabel
key={index}
control={
<Checkbox
checked={checked['brands'].indexOf(brand) > -1}
onClick={e => handleChange(e, 'brands')}
name={brand}
checkedIcon={<CheckBoxIcon
sx={{
color: "#82BF37"
}}
/>}
/>
}
label={brand}
/>
)
})}
</FormGroup>
</FormControl>
</Box>
)
And the parent component:
const ProductList = ({ products, filter = {
brands: [],
types: [],
ages: [],
breeds: [],
features: [],
petTypes: []
} }) => {
const [filteredProducts, setFilteredProducts] = useState(products)
const classes = useStyles()
const filterModel = getFilterModel(products)
const [checked, setChecked] = useState(filter)
const handleChange = (event, group) => {
console.log(event)
let checkedOptn = [...checked[group]]
setChecked({
...checked,
[group]: checkedOptn.indexOf(event.target.name) > -1 ? checkedOptn.filter(item => item !== event.target.name) :
[...checkedOptn, event.target.name]
})
}
React.useEffect(() => {
const filtered = filterPrdcts(products, checked)
setFilteredProducts(filtered)
}, [checked,products])
return (
<Grid className={classes.wrapper} container >
<Grid item xs={3} sx={{ paddingTop: '10px' }}>
<FilterComponent options={filterModel} handleChange={handleChange} checked={checked} />
</Grid>
<Grid item xs={9} sx={{ paddingTop: '10px' }}>
<div className={classes.productListWrapper}>
{filteredProducts?.map((product, index) => {
return (
<ProductCard product={product} key={index} />
)
})}
</div>
</Grid>
</Grid>
)
}
I pass the event controller function (handleChange) to the child event but does not work.
Appreciate help.
You need to use onChange not onClick for handleChange event.
Sorry to bother you all. It was because the zIndex value on the wrapper element. When I removed the zIndex value, everything started to work fine.
I need to put all the values and input field for multi autocomplete on one line. But the input field sliding down.
My code:
const { filter, classes, options } = this.props;
const style = filter && filter.value !== '' ? {backgroundColor: 'lavender'} : {};
return (
<TableFilterRow.Cell { ...this.props } className={ classes.cell } style={ style }>
<Autocomplete
options={options}
value={options.filter(option => filter.includes(option.value)) || []}
getOptionLabel={option => option.label}
multiple={true}
fullWidth
disableClearable={true}
onChange={this.handleFilter}
renderOption={option => (
<React.Fragment>
<Checkbox
color="primary"
checked={filter.includes(option.value) || false}
/>
{option.label}
</React.Fragment>
)}
renderTags={values => values.map(option => option.label).join(', ')}
renderInput={(params) => (
<TextField
{...params}
fullWidth
margin="dense"
/>
)}
/>
</TableFilterRow.Cell>
Result:
How can I put all the values and input field for multi autocomplete on one line?
Use style hook API to select input and apply flexWrap and overflow CSS properties:
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
"& > .MuiAutocomplete-root .MuiFormControl-root .MuiInputBase-root": {
flexWrap: "nowrap",
overflowX: "scroll" // or "hidden"
}
}
}));
then, on component use on className:
...
const classes = useStyles();
return (
<div className={classes.root}>
<Autocomplete
...
See this working on codesandbox here.
I've got the following code where I try to capitalize the values shown as labels inside MenuItem but does not apply the text transformation. Any ideas?
const values = ['some value', 'some value 2'];
<TextField
id="status"
name="status"
select
label="Status"
onChange={this.handleSearch}
value={filter.status}
className={classes.textField}
>
{campaignStatus.map(status => (
<MenuItem key={status} value={status} style={{ textTransform: "capitalize" }}>
{status}
</MenuItem>
))}
</TextField>
this is because material ui has their own system of overriding styles, you have to use this syntax
import { makeStyles } from "#material-ui/core/styles";
const Function = props => {
const useStyles = makeStyles(theme => ({
center: {
textTransform: "capitalize"
}
}));
const classes = useStyles();
const values = ["some value", "some value 2"];
return (
<TextField
id="status"
name="status"
select
label="Status"
onChange={this.handleSearch}
value={filter.status}
className={classes.textField}
>
{campaignStatus.map(status => (
<MenuItem key={status} value={status} className={classes.center}>
{status}
</MenuItem>
))}
</TextField>
);
};