Uncheck the parent checkbox on unchecking child elements in React App - reactjs

I have a check for Select All to check all/Uncheck all the child elements but when I uncheck any of the child elements I want to uncheck the parent checkbox as well.
I can check and uncheck all when I select the SELECT ALL, but cannot achieve vice versa when any of the child element is unchecked.
export const List = props => {
const{startOver} = props;
const onCheckAllcheckbox = (event) => {
let _items = props.items.map(item => {
item.isChecked = event.target.checked
return item;
});
props.setItems(_items);
}
const handleCheckChieldElement = (event) => {
let _items = props.items.map(item => {
if (item.id == event.target.value) {
item.isChecked = event.target.checked
}
return item;
})
props.setItems(_items);
}
return (
<Box>
{props.action && (
<Box horizontal align="center">
<input type="checkbox"
onChange={onCheckAllcheckbox}
defaultChecked={true}
style={{width: '20px', height: '15px'}} />
<Text>SELECT ALL</Text>
</Box>
)}
<div style={{overflow : 'auto', height : '200px', width : '900'}}>
<Box type="flat">
{props.items.map((item, index) => (
<Box key={index} horizontal align="center" style={{ margin: '.3rem 0' }}>
{props.action && (
<input type="checkbox"
onChange={handleCheckChieldElement}
checked={item.isChecked}
value={item.id}
style={{width: '20px', height: '15px'}} />
)}
{props.itemTemplate && props.itemTemplate(item) || (
<Text>{item.text}</Text>
)}
</Box>
))}
</Box>
</div>
</Box>
)
}

Add checked attribute to Select All input instead of passing defaultChecked attribute.
<Box horizontal align="center">
<input type="checkbox"
onChange={onCheckAllcheckbox}
checked={props.items.every((item) => item.isChecked)}
style={{width: '20px', height: '15px'}} />
<Text>SELECT ALL</Text>
</Box>

Related

How to stop modal from closing when clicking on a select option?

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>
...

React picky not auto closing while selecting one value,?

If using single select, is it possible to close the dropdown after selection? I tried passing keepOpen props but cant able to fix, whiling passing keepOpen the drop down not working as it would, How can fix this issue?
console log while clicking drop down
console log
Parent Component Input Feild
<Grid item xs={12} sm={12} md={12} className={classes.item}>
<Field name='gender' label={I18n.t('gender')} filterable={false} component={renderSelect} >
{
_.get(getGender, 'data', genderArray)
}
</Field>
</Grid>
<Grid item xs={12} sm={12} md={12} className={classes.item}>
<Field name='userTypeResponse' label={I18n.t('user_type')} component={renderSelect} filterable={false} >
{
data?.map(item => ({ id: item.id, name: item.name }))
}
</Field>
</Grid>
Selector Component
const renderSelect = ({ input: { value, name, ...inputProps }, children, selectAll, selectAllText = 'Select All', filterable = true, multiple, label, disabled = false, placeholder = '', showLoader = false, spinnerProps = 'selectTagProp', meta: { touched, error } }) => {
return < LoadingOverlay active={showLoader} spinnerProps={spinnerProps} >
<FormGroup>
{label && <div className='customPicky'>
<InputLabel htmlFor={name}>{label}</InputLabel>
</div>}
<MultiSelect
value={multiple ? value || [] : value || null} {...inputProps}
options={children}
open={false}
multiple={multiple}
keepOpen={false}
includeSelectAll={selectAll}
selectAllText={selectAllText}
includeFilter={filterable}
labelKey='name'
valueKey='id'
placeholder={placeholder}
dropdownHeight={150}
clearFilterOnClose={true}
defaultFocusFilter={true}
disabled={disabled}
className={disabled === true ? 'pickySelect grey' : null}
numberDisplayed={1}
renderList={({ items, selectValue, getIsSelected }) => {
return <div >
{items.length < 1 && <div style={{ textAlign: 'center', padding: '5px' }}>{I18n.t('no_data_available')}</div>}
{items.map(item => (
<li key={item.id} onClick={() => selectValue(item)} style={{ display: 'flex', alignItems: 'center' }}>
<input type={multiple ? 'checkbox' : 'radio'} id={item.id} checked={getIsSelected(item)} />
<div style={{ marginLeft: '5px' }} >
{item.name}
</div>
</li>
))}
</div>;
}
}
/>
{touched && error && <Typography color="error" variant="caption">{error}</Typography>}
</FormGroup>
</LoadingOverlay >;
};

How can I add the 'All' checkbox in order to select all the other options?

how can I add the All checkboxes that will select all the other checkboxes for each Type of plant and adding a checkbox in front of each Type of plant section. So when I select one option Plant 1.1 then my checkbox for Type of plant #1 is filled, and if option is filled then the checkbox for Type of plant is not filled.
export default function Category({
_id_type,
name_type,
plants,
changeSelection
}) {
const [toggleState, setToggleState] = useState(false);
return (
<div key={_id_type}>
<div
style={{
cursor: "pointer",
userSelect: "none",
display: "flex",
margin: "2px",
backgroundColor: "lightgray"
}}
onClick={() => setToggleState((prev) => !prev)}
>
<div>{name_type}</div>
<div
style={{
backgroundColor: "blue",
color: "white",
padding: "0px 10px",
marginLeft: "auto"
}}
>
{plants.filter(({ selected }) => selected).length}
</div>
</div>
<div style={{ marginLeft: "10px" }}>
{toggleState &&
plants.map(({ name, _id, selected }) => (
<div key={_id}>
<input
key={_id}
type="checkbox"
value={name}
checked={selected}
onChange={(e) => changeSelection(_id_type, _id, e.target.value)}
/>
{name}
</div>
))}
</div>
</div>
);
}
Here a picture (what I have/ what I want) :
Here is my code
add new toogle inside category.jsx
{toggleState && plants.length > 1 ? (
<div>
<input
type="checkbox"
value={"all"}
checked={allSelected}
onChange={(e) => {
setAllSelected((v) => {
changeSelection(_id_type, "all", e.target.value, !v);
return !v;
});
}}
/>
All
</div>
) : (
""
)}
edit change selection function:
const changeSelection = (catId, itemId, value, allSelected) => {
setSelectionMenu((prevSelectionMenu) =>
prevSelectionMenu.map((item) => {
if (item._id_type === catId) {
return {
...item,
plants: item.plants.map((plant) => {
if (plant._id === itemId) {
return { ...plant, selected: !plant.selected };
} else if (itemId === "all") {
return { ...plant, selected: allSelected };
}
return plant;
})
};
}
return item;
})
);
};
here the forked code:
https://codesandbox.io/embed/plants-forked-qdmz2h?fontsize=14&hidenavigation=1&theme=dark

Mui Checkbox Event not firing when clicked

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.

React js click outside of the component does not work

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

Resources