Mui Checkbox Event not firing when clicked - reactjs

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.

Related

Is there a way to register custom components with useForm?

I am creating a web form using useForm(). This is my main component:
function InsertComponent() {
const [category, setCategory] = useState<number>(-1);
const { register, handleSubmit, control } = useForm({ shouldUnregister: false });
const onSubmit = (data: any, e: any) => {
console.log(control);
console.log(data, e);
}
const changeCategory = (category: number) => {
setCategory(category);
}
return (
<Container component={'main'} maxWidth={'xs'}>
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography component={'h1'} variant={'h5'}>
Nuevo gasto
</Typography>
<Box component={'form'} noValidate sx={{ mt: 3 }} onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField label={'Cantidad'} autoComplete="given-name" id="quantity" required fullWidth autoFocus {...register('quantity')}></TextField>
</Grid>
<Grid item xs={12} sm={6}>
<SelectComponent options={MONTHS} label={'Month'} />
</Grid>
<Button type="submit" style={{ marginTop: '20px' }} variant="outlined">Guardar</Button>
</Box>
</Box>
</Container>
)
}
I can register data with {...register('quantity')} in TextField and that works fine. The problem is I am not finding a way to register the options selected in the SelectComponent component. This is the component:
function SelectComponent(props: SelectCategory) {
const { options, label, onChange, disabled, control } = props;
const { show } = useWatch({ control })
const handleChangeCategory = (event: SelectChangeEvent) => {
onChange && onChange(parseInt(event.target.value))
}
return (
<Box sx={{ minWidth: 150 }}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">{label}</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
disabled={disabled}
label={label}
onChange={handleChangeCategory}
>
{options.map((option, index) => <MenuItem value={index}>{option}</MenuItem>)}
</Select>
</FormControl>
</Box>
)
}
I've been reading the docs and trying npm libraries like useStateMachine but I haven't gotten it to work. The value of SelectComponent is never registered on the InsertComponent and never appears in the data of the onSubmit function.

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

Problems integrating react-hooks-forms with material ui

**Hello folks,
my problem is there are no error messages displayed even if nothing is inside. Anyone know what I did wrong? The code is in the attachment.Everything works except the message when nothing was selected in the autocomplete
.
.
.
Am I doing it wrong with the formprovider? Or should I better take the register method from the useForm - Methods? Or schould the text field itself have a controller?
**
const validationSchema = yup.object().shape({
selKategorie: yup.string().required("Select Validation Field is Required"),
}).required();
function Anlage(props) {
const { status, data, isError, isFetching } = useAnlegeData();
const [step, setStep] = React.useState(0);
const methods = useForm({
resolver: yupResolver(validationSchema),
});
const { handleSubmit, formState: { errors }, watch } = methods;
const labels = ['First Step', 'Secound Step', 'Confirmation'];
const handleSteps = (step) => {
switch (step) {
case 0:
return <FirstStep kategorien={data.Kategorie} methods={methods} />
case 1:
return <SecoundStep />
case 2:
return <Confirm />
default:
return <FirstStep />
}
}
return(
<>
<MyNavbar BACK={true}/>
{status.loading && <Loading /> }
<Container component="main" sx={{ mb: 4}}>
<Paper variant="outlined"
sx={{ my: { xs: 3, md: 6 }, p: { xs: 2, md: 3 } }}>
<Stepper activeStep={step} sx={{ py: 3 }} alternativeLabel>
{labels.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
{status === 'success' &&
handleSteps(step)}
</Paper>
<p>{errors?.selKategorie?.message}</p>
</Container>
</>
)
}
export default function FirstStep(props) {
return(
<>
<FormProvider {...props.methods}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<ControlledAutocomplete
name="selKategorie"
options={props.kategorien}
getOptionLabel={(option) => {
console.log('get option label: ',option);
return option.MASKENKEY; }
}
renderInput={(params) => <TextField {...params} label="Ticketkategorie" margin="normal" />}
defaultValue={null}
/>
</Grid>
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="contained"
sx={{ mt: 3, ml: 1 }}
disabled={false}
color="primary"
onClick={() => null}
>
Next
</Button>
</Box>
</FormProvider>
</>
)
}
const ControlledAutocomplete = ({ options = [], renderInput, getOptionLabel, defaultValue, name}) => {
const { control, formState: { errors } } = useFormContext();
let isError = true;
let errorMessage = "";
if (errors && errors.hasOwnProperty(name)) {
isError = true;
errorMessage = errors[name].message;
}
return (
<Controller
render={({ field: { onChange } }) => (
<Autocomplete
options={options}
getOptionLabel={getOptionLabel}
//renderOption={renderOption}
renderInput={renderInput}
onChange={onChange}
/>
)}
onChange={([, data]) => {
return data
}}
name={name}
control={control}
defaultValue={defaultValue}
errorMessage={errorMessage}
error={isError}
/>
)
}

Undesired scrolling in React on rendering component

I have a component where the search results of a query are displayed. When clicking on a item to view its details, instead of loading normally (scrolled all the way up), the details page is, somehow, being affected by the scroll position of the results page. I know I could probably solve this by including window.scrollTo({ top: 0, left: 0 }), but I just want it to behave normally like it should and eliminate what's causing this issue.
Results.js
function Results() {
const matches = useMediaQuery("(max-width:767px)");
const navigate = useNavigate();
const [details, setDetails] = useState({ results: [] });
// Create a custom hook that binds to the `search` property from `location` url object
function useQuery() {
const { search } = useLocation();
return React.useMemo(() => new URLSearchParams(search), [search]);
}
let query = useQuery().toString();
useEffect(() => {
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
// On successful response, assign the results to `results` array in `details` object
axios
.get(`/api/results/${query}`)
.then((response) => {
setDetails(response.data);
})
.catch((error) => {
console.log(error);
});
}, [query]);
const results = details.total_results;
const pages = details.total_pages;
const baseURL = "https://image.tmdb.org/t/p/w154";
const placeholderImg =
"https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg";
return (
<CustomCard
title="Results"
content={
<div>
<Grid container>
<Typography>{results ? results : 0} results found</Typography>
<Grid item xs={12}>
<List sx={{ mt: 3 }}>
{details.results.map((result) => {
return (
<ResultItem
key={result.id}
id={result.id}
imgURL={
result.poster_path
? baseURL + result.poster_path
: placeholderImg
}
title={result.title}
year={result.release_date}
synopsis={result.overview}
/>
);
})}
</List>
</Grid>
</Grid>
<Pagination
sx={{ mt: 2 }}
count={pages}
siblingCount={matches ? 0 : 1}
onChange={(event, page) => {
const url = new URLSearchParams(window.location.search);
url.set("page", page);
const query = url.toString();
navigate(`/results?${query}`);
}}
/>
</div>
}
/>
);
}
export default Results;
ResultItems.jsx
function ResultItem(props) {
const navigate = useNavigate();
const matches = useMediaQuery("(max-width:767px)");
return (
<div>
<ListItem disablePadding>
<ListItemButton alignItems="flex-start" sx={{ pl: 0, pt: 2 }}>
<div>
<img
src={props.imgURL}
alt={`${props.title} poster`}
style={
matches
? { width: "80px", height: "120px" }
: { width: "154px", height: "231px" }
}
/>
</div>
<ListItemText
sx={{ ml: 3 }}
primary={props.title}
secondary={
<React.Fragment>
<Typography component="span" variant="body2">
{props.year.slice(0, 4)}
</Typography>
<br />
<br />
{matches ? null : (
<span style={{ textAlign: "justify" }}>{props.synopsis}</span>
)}
</React.Fragment>
}
onClick={() => navigate(`/details/${props.id}`)}
/>
</ListItemButton>
</ListItem>
<Divider />
</div>
);
}
export default ResultItem;
Details.js
function Details() {
const matches = useMediaQuery("(max-width:718px)");
const navigate = useNavigate();
const [details, setDetails] = useState({
tmdbDetails: { genres: [] },
tmdbCredits: { cast: [], crew: [] },
userData: { rating: null, review: "", view_count: null },
});
const [show, setShow] = useState(false);
const [isUpdated, setIsUpdated] = useState(false);
// GET MOVIE DETAILS AND USER LOGGED DATA
useEffect(() => {
const url = new URL(window.location.href);
axios
.get(`/api${url.pathname}`)
.then((response) => {
setDetails(response.data);
})
.catch((err) => {
console.log(err);
});
}, [isUpdated]);
const baseURL = "https://image.tmdb.org/t/p/w342";
const placeholderImg =
"https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg";
// Expand section to log new diary entry or view/edit previous logged data
function handleExpand() {
setShow(!show);
}
// ENTRY DATA
const [userData, setUserData] = useState({
rating: null,
review: "",
date: new Date(),
});
// New object passing user data and movie data to be saved on database
const entryData = {
...userData,
title: details.tmdbDetails.title,
year: getYear(details),
director: getDirector(details),
genres: getGenres(details),
runtime: details.tmdbDetails.runtime,
};
// Control value on Date Picker and set it to `userData.date`
function handleDate(date) {
setUserData((prevValue) => {
return {
...prevValue,
date: date.toISOString(),
};
});
}
// Control value on Rating selector and set it to `userData.rating`
function handleRating(event, value) {
setUserData((prevValue) => {
return {
...prevValue,
rating: value,
};
});
}
// Control value on Text Area and set it to `userData.review`
function handleReview(event) {
const { value } = event.target;
setUserData((prevValue) => {
return {
...prevValue,
review: value,
};
});
}
// Submit entry to database and navigate to Diary
function onSubmit(event) {
event.preventDefault();
const url = new URL(window.location.href);
axios.post(`/api${url.pathname}`, entryData).then((res) => {
navigate("/diary");
});
}
// Function passed to the "WatchedPanel" component to be executed on saving changes after edit entry. It changes `isUpdated` state to force a re-render of `useEffect()` and update entry data on-screen
function handleUpdateDetails() {
setIsUpdated(!isUpdated);
}
return (
<Container component="main" maxWidth="md">
<Card sx={{ padding: matches ? 0 : 3, paddingBottom: 0, margin: 2 }}>
<div style={{ textAlign: "right" }}>
<IconButton
aria-label="close"
style={{ color: "#e0e0e0" }}
onClick={() => {
navigate(-1);
}}
>
<CloseIcon />
</IconButton>
</div>
<CardContent>
<Grid container alignItems="center">
{/* MOVIE TITLE & YEAR */}
<Grid item xs={12}>
<Typography variant="h5">{details.tmdbDetails.title}</Typography>
<Typography sx={{ mb: 2 }} variant="h6">
({getYear(details)})
</Typography>
</Grid>
{/* MOVIE POSTER */}
<Grid item xs={12} sm={matches ? 12 : 5} md={4}>
<div style={{ textAlign: matches ? "center" : "left" }}>
<Poster
source={
details.tmdbDetails.poster_path
? baseURL + details.tmdbDetails.poster_path
: placeholderImg
}
altText={`${details.tmdbDetails.title} poster`}
/>
</div>
</Grid>
{/* MOVIE DETAILS */}
<Grid item xs={12} sm={7} md={8}>
<Collapse in={matches ? show : true}>
<Credits
director={getDirector(details).join(", ")}
cast={getCast(details)}
genres={getGenres(details).join(", ")}
runtime={details.tmdbDetails.runtime}
/>
</Collapse>
</Grid>
<Grid item xs={12} sx={{ mt: 2 }}>
<Collapse in={matches ? show : true}>
<Typography style={{ fontWeight: "bold" }}>Synopsis</Typography>
<Typography>{details.tmdbDetails.overview}</Typography>
</Collapse>
</Grid>
{/* EXPAND SECTION BUTTON */}
{matches ? (
<Fab
color="primary"
size="small"
aria-label="expand"
sx={{ m: "auto", mt: 2 }}
onClick={handleExpand}
>
{show ? <RemoveIcon /> : <AddIcon />}
</Fab>
) : null}
</Grid>
</CardContent>
</Card>
{/* LOG MOVIE PANEL */}
{details.userData === undefined ? (
<UnwatchedPanel
date={userData.date}
onDateChange={handleDate}
rating={userData.rating}
onRatingChange={handleRating}
review={userData.review}
onReviewChange={handleReview}
view_count={
details.userData === undefined ? null : details.userData.view_count
}
onClick={onSubmit}
/>
) : (
<WatchedPanel
date={userData.date}
onDateChange={handleDate}
rating={details.userData.rating}
review={details.userData.review}
view_count={details.userData.view_count}
onSubmit={onSubmit}
onSaveChanges={handleUpdateDetails}
/>
)}
</Container>
);
}
export default Details;
My mistake, actually: this is, indeed, the normal React behavior. If you're navigating from one (scrollable) component to another the scrollbar isn't aware of it, since you're just changing views without reloading the whole page, and will keep it's y (and x) position. To avoid this behavior you need to manually set the scroll position on re-render, like so:
useEffect(() => {
window.scrollTo({ top: 0, left: 0 });
// Do some stuff
}, []);

ClickAwayListener component not working with DragDropContext

I made a dropdown using Button and a Popper using Material UI components where you can click on the button and get a list of subjects to choose from. To make the popper disappear either we can click on the button again or use a <ClickAwayListener> component which listens to click event and closes the Popper. Now I've to make the list capable of drag and drop feature so I use the react-beautiful-dnd npm package.But the <ClickAwayListener> doesn't seem to work when I include <DragDropContext>, <Droppable> and <Draggable> components.Can anyone help me figure it out?
Here's the code without drag and drop feature. CodeSandbox link https://codesandbox.io/s/gallant-newton-mfmhd?file=/demo.js
const subjectsFromBackend = [
{ name: "Physics", selected: false },
{ name: "Chemistry", selected: false },
{ name: "Biology", selected: false },
{ name: "Mathematics", selected: false },
{ name: "Computer Science", selected: false },
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
paper: {
marginRight: theme.spacing(2)
}
}));
export default function MenuListComposition() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [subjects, setSubjects] = React.useState(subjectsFromBackend);
const handleToggle = () => {
setOpen(!open);
};
const handleClose = () => {
setOpen(false);
};
const ColumnItem = ({ subjectName, selected }) => {
return (
<>
<Grid container>
<Grid item>
<Checkbox checked={selected} />
</Grid>
<Grid item>{subjectName}</Grid>
</Grid>
</>
);
};
return (
<div className={classes.root}>
<div>
<Button
ref={anchorRef}
onClick={handleToggle}
style={{ width: 385, justifyContent: "left", textTransform: "none" }}
>
{`Subjects Selected`}
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<Paper style={{ maxHeight: 200, overflow: "auto", width: 385 }}>
<ClickAwayListener onClickAway={handleClose}>
<List>
{subjects.map((col, index) => (
<ListItem>
<ColumnItem
subjectName={col.name}
selected={col.selected}
/>
</ListItem>
))}
</List>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
);
}
Here's the same code using drag and drop. CodeSandbox Link https://codesandbox.io/s/material-demo-forked-ertti
const subjectsFromBackend = [
{ name: "Physics", selected: false },
{ name: "Chemistry", selected: false },
{ name: "Biology", selected: false },
{ name: "Mathematics", selected: false },
{ name: "Computer Science", selected: false },
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
paper: {
marginRight: theme.spacing(2)
}
}));
export default function MenuListComposition() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [subjects, setSubjects] = React.useState(subjectsFromBackend);
const handleToggle = () => {
setOpen(!open);
};
const handleClose = () => {
setOpen(false);
};
const ColumnItem = ({ subjectName, selected }) => {
return (
<>
<Grid container>
<Grid item>
<Checkbox checked={selected} />
</Grid>
<Grid item>{subjectName}</Grid>
</Grid>
</>
);
};
const onDragEnd = (result, subjects, setSubjects) => {
const { source, destination } = result;
if (!destination) return;
if (source.droppableId !== destination.droppableId) return;
const copiedItems = [...subjects];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setSubjects(copiedItems);
};
return (
<div className={classes.root}>
<div>
<Button
ref={anchorRef}
onClick={handleToggle}
style={{ width: 385, justifyContent: "left", textTransform: "none" }}
>
{`Subjects Selected`}
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<DragDropContext
onDragEnd={(res) => onDragEnd(res, subjects, setSubjects)}
>
<Paper style={{ maxHeight: 200, overflow: "auto", width: 385 }}>
<ClickAwayListener onClickAway={handleClose}>
<Droppable droppableId={"subjectsColumn"}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
<List>
{subjects.map((col, index) => (
<Draggable
key={col.name}
draggableId={col.name}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ListItem>
<ColumnItem
subjectName={col.name}
selected={col.selected}
/>
</ListItem>
</div>
)}
</Draggable>
))}
</List>
{provided.placeholder}
</div>
)}
</Droppable>
</ClickAwayListener>
</Paper>
</DragDropContext>
</Grow>
)}
</Popper>
</div>
</div>
);
}
I found out that the ClickAwayListener's child component should be wrapped around a div so that the click event could be triggered properly.
You need to have your ClickAwayListener at the top when you are using the Drag and Drop.
return (
<ClickAwayListener
onClickAway={() => {
console.log("i got called");
handleClose();
}}
>
.....
</ClickAwayListener>
Here is the working codesandbox - https://codesandbox.io/s/material-demo-forked-h1o1s?file=/demo.js:4877-4897

Resources