Undesired scrolling in React on rendering component - reactjs

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
}, []);

Related

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.

My react component return statement fails to render but console.log shows exactly what I need

I am new to react and working on a react video player. I'm having issue implementing the comment section.
This is my videoplayer component itself.
export default function VidPlayer() {
// useStates
const [state, setState] = useState({
playing: true,
});
const [comments, setComments] = useState([]);
const [comment, setComment] = useState("");
const { playing } = state;
const playerRef = useRef(null);
// declaring functions for video player buttons
const handlePlayPause = () => {
setState({ ...state, playing: !state.playing });
};
const handleRewind = () => {
playerRef.current.seekTo(playerRef.current.getCurrentTime() - 5);
};
const handleFoward = () => {
playerRef.current.seekTo(playerRef.current.getCurrentTime() + 5);
};
const handleStop = () => {
playerRef.current.seekTo(0);
setState({ playing: !state.playing });
};
// declaring functions for comment section
const addComments = () => {
if (comment) {
setComments({...comments, comment});
setComment("");
console.log("Hello", comments);
}
};
const handleComment = (e) => {
setComment(e.target.value);
};
return (
<div>
<AppBar style={{ background: "#e6880e" }} position="static">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Favour's Video Player
</Typography>
</Toolbar>
</AppBar>
{/* container for the videoplayer, buttons and comment section */}
<div className="container">
<>
{/* videoplayer */}
<div className="reactPlayer one">
<ReactPlayer
ref={playerRef}
url="https://www.youtube.com/watch?v=1_ATK0BLc8U&t=3s"
playing={playing}
controls
/>
</div>
{/* buttons */}
<div className="btn-stack two">
<Stack spacing={2} direction="row">
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handlePlayPause}
>
Play
</Button>
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handleRewind}
>
Rewind{" "}
</Button>
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handleFoward}
>
Forward{" "}
</Button>
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handleStop}
>
Stop
</Button>
</Stack>
</div>
{/* comment section */}
<div className="comment three">
<Comment userComs={comments} />
<TextField
placeholder="add comment"
size="small"
variant="outlined"
onChange={handleComment}
value={comment}
/>
<Button
style={{ background: "#e6880e", marginLeft: '1em' }}
onClick={addComments}
variant="contained"
size="small"
>
Send
</Button>
</div>
</>
</div>
</div>
);
}
It takes in this comments component towards the end.
export default function commentList(props) {
console.log("Hello brian", props.userComs);
const { userComs } = props;
if (Object.keys(userComs).length > 0) {
console.log(userComs);
// userComs.forEach((element) => {
// console.log("Im in", userComs);
Object.values(userComs).forEach(val => {
// console.log("Im in", userComs);
// console.log(val);
return (
<div>
<List
sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}
>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
secondary={
<React.Fragment>
<Typography
sx={{ display: "inline" }}
component="span"
variant="body2"
color="text.primary"
>
Ali Connors
</Typography>
{val}
</React.Fragment>
}
/>
</ListItem>
<Divider variant="inset" component="li" />
</List>
</div>
);
});
} else {
return <div></div>;
}
}
This is the Front-End enter image description here
Unfortunately, when I enter a comment and click send, the screen goes blank and console throws a "nothing was returned from render" error. Can someone help me check what is wrong and how I can fix this please?
As the error says, the component isn't returning anything.
Object.values(userComs).forEach(val => {
should be
return Object.values(userComs).map(val => {
because forEach doesn't return anything and the JSX returned in each iteration will not be used anywhere, but map returns a new array that React can use.
BTW make sure to give a key prop to each div that is returned from the callback.
<div key={val}> // assuming `val` is unique

Showing Circular indeterminate Progress on button click

I have a button that when clicked runs some code that imports data from another website. What would be nice is to have a Circular indeterminate indicator (Material UI) that shows the import is in progress, until completion.
My code below shows the code I am running and some of the jsx.
<Box mt={5} mb={5} width={1200}>
<Grid
container
justify="center"
spacing={2}
style={{ padding: '25px' }}
>
<Grid item xs={7}>
<Typography variant="h3" style={{ fontWeight: 600 }}>
List
</Typography>
<Typography variant="h5" style={{ marginTop: '2em' }}>
some text
</Typography>
<Button
variant="contained"
color="primary"
size="large"
style={{ marginTop: '1em' }}
// style={{ maxWidth: '108px', minWidth: '108px' }}
onClick={() => {
let arrCollection = [];
const stream = fetch(
'https://othersite.org/api/games/user/neio',
{ headers: { Accept: 'application/x-ndjson' } }
);
const onMessage = obj => {
arrCollection.push(obj);
};
const onComplete = () =>
console.log('The stream has completed');
stream.then(readStream(onMessage)).then(onComplete);
console.log('arrCollection', arrCollection);
}}
>
Import Games
</Button>
</Grid>
</Grid>
</Box>
What I can't work out is how to display the "circular indeterminate" while importing and then show it completed.
This is the link to the circular indeterminate code: https://material-ui.com/components/progress/
Do I need to add a node to the Dom or something?
As mentioned already, you can use state to manage this.
Something like this should work
const [loading, setLoading] = useState(false);
<div>{loading ? <LoadingIndicator /> : ''}</div>
<Button
onClick={() => {
setLoading(true);
let arrCollection = [];
const stream = fetch('https://othersite.org/api/games/user/neio', {
headers: { Accept: 'application/x-ndjson' },
});
const onMessage = (obj) => {
arrCollection.push(obj);
};
const onComplete = () => console.log('The stream has completed');
stream.then(readStream(onMessage)).then(onComplete);
console.log('arrCollection', arrCollection);
setLoading(false);
}}
>
Import Games
</Button>

Uncaught TypeError: Cannot read properties of undefined (reading 'source')

I'm trying to access data from commerceJS API fetched on a different module and passed down as a prop, the code only breaks when I call a nested product object with this error Uncaught TypeError: Cannot read properties of undefined (reading 'source').
I have used the same API on the storefront and {product.media.source} works fine. I'm trying to make a preview module and when I call the nested object it breaks.
Please help.
Here's my code
const Preview = ({ products, onAddToCart }) => {
const [product, setProduct] = useState({});
const params = useParams();
const findProduct = (id) => {
return products.find((product) => {
setProduct(product);
return product.id === id;
});
};
const productId = params.id;
useEffect(() => {
findProduct(productId);
});
return (
<Card sx={{ maxWidth: 345 }} style={{ marginTop: '90px' }}>
<CardMedia image={product.media.source} title={product.name}></CardMedia>
<CardContent>
<Typography gutterBottom variant='h5' component='div'>
{product.name}
</Typography>
<Typography
variant='body2'
color='textSecondary'
dangerouslySetInnerHTML={{ __html: product.description }}
></Typography>
</CardContent>
<CardActions>
<IconButton component={Link} variant='outlined' to='/'>
Go Back
</IconButton>
<IconButton aria-label='Add to Cart' onClick={() => onAddToCart(product.id, 1)}>
<AddShoppingCart />
</IconButton>
</CardActions>
</Card>
);
Here's the product Object
product obj
Apparently your product object does not contain media field by default. You need to wait until it is available before using it.
Try something like this:
const Preview = ({ products, onAddToCart }) => {
const [product, setProduct] = useState();
const params = useParams();
console.log(products[0]);
console.log(params.id);
const findProduct = useCallback((id) => {
return products.find((product) => {
if (product.id === id) { // <-- render optimization
setProduct(product);
return true;
}
return false;
});
}, [products]);
const productId = useMemo(() => params.id, [params.id]);
useEffect(() => {
findProduct(productId);
}, [productId, findProduct]);
const renderCard = useCallback(() => {
if (!product) return null; // <-- here you can implement your placeholder component
return (
<Card sx={{ maxWidth: 345 }} style={{ marginTop: '90px' }}>
<CardMedia image={product.media.source} title={product.name}/>
<CardContent>
<Typography gutterBottom variant='h5' component='div'>
{product.name}
</Typography>
<Typography
variant='body2'
color='textSecondary'
dangerouslySetInnerHTML={{ __html: product.description }}
></Typography>
</CardContent>
<CardActions>
<IconButton component={Link} variant='outlined' to='/'>
Go Back
</IconButton>
<IconButton aria-label='Add to Cart' onClick={() => onAddToCart(product.id, 1)}>
<AddShoppingCart />
</IconButton>
</CardActions>
</Card>
);
}, [product]);
return renderCard();
};
Or like this:
const initialState = { // <-- you need to change this as you need
active: true,
assets: [/* ... */],
categories: [/* ... */],
checkout_url: {
checkout: 'placeholder://checkout.url',
display: 'placeholder://display.url',
/* ... */
},
collects: {/*...*/}
created: 0,
description: 'placeholder description',
extra_fields: [/* ... */],
hsa: {/* ... */},
id: 'placeholder_id',
invenotry: {/* ... */},
is: {/* ... */},
media: {
type: 'image',
source: 'placeholder://image.source',
/* ... */
},
meta: null,
/* ... */
};
const Preview = ({ products, onAddToCart }) => {
const [product, setProduct] = useState(initialState);
const params = useParams();
console.log(products[0]);
console.log(params.id);
const findProduct = useCallback((id) => {
return products.find((product) => {
if (product.id === id) { // <-- render optimization
setProduct(product);
return true;
}
return false;
});
}, [products]);
const productId = useMemo(() => params.id, [params.id]);
useEffect(() => {
findProduct(productId);
}, [productId, findProduct]);
return (
<Card sx={{ maxWidth: 345 }} style={{ marginTop: '90px' }}>
<CardMedia image={product.media.source} title={product.name}/>
<CardContent>
<Typography gutterBottom variant='h5' component='div'>
{product.name}
</Typography>
<Typography
variant='body2'
color='textSecondary'
dangerouslySetInnerHTML={{ __html: product.description }}
></Typography>
</CardContent>
<CardActions>
<IconButton component={Link} variant='outlined' to='/'>
Go Back
</IconButton>
<IconButton aria-label='Add to Cart' onClick={() => onAddToCart(product.id, 1)}>
<AddShoppingCart />
</IconButton>
</CardActions>
</Card>
);
};
Based on your code above
console.log(products[0]);
console.log(params.id);
const findProduct = (id) => {
return products.find((product) => {
setProduct(product);
return product.id === id;
});
};
Looks like the product is an array and not an object. I think this is where you might have missed.
Hence, product.media.source throws error as product.media is undefined.
Please check which product you might want to add.

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