How can I only expand the area that I have clicked on?
I have already tried to pass the index of the map function into the handleExpandClick () function, but that didn't work either
For the moment, all cars are expanded if i click at one.
export default function Card({ wert, ergebnis }) {
const [expanded, setExpanded] = React.useState(false);
const handleExpandClick = () => {
setExpanded(!expanded);
};
[...]
{ergebnis.map((e, index) => (
[...]
<IconButton
className={clsx(classes.expand, {[classes.expandOpen]: expanded,})}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
[...]
<Collapse in={expanded} timeout="auto" unmountOnExit>[...]</Collapse>
[...]
))}
I'd use an object to track each elements expanded state, something like:
const [expanded, setExpanded] = React.useState({});
const handleExpandClick = (index) => {
setExpanded({
...expanded,
[index]: !expanded[index]
});
};
<IconButton
className={clsx(classes.expand, {[classes.expandOpen]: !!expanded[index],})}
onClick={() => handleExpandClick(index)}
aria-expanded={expanded}
aria-label="show more"
>
<Collapse in={!!expanded[index]} timeout="auto" unmountOnExit>[...]</Collapse>
Related
Here is the menu UI
Trying to load menu and menu item dynamically from backend through redux state.not able render menu items for specific menu.
json Data structre:
menuContext:{
Define:[{id:1,label:"user"},{id:2,label:"test"}]
Manage:[{id:1,label:"test2"},{id:2,label:"test3"}]
}
function MenuContext() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const handleClick = (event) => {
console.log(event.currentTarget)
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const { viewService, isError, message } = useSelector(state => state.auth)
const [menu, setMenu] = useState([])
const [items, setItems] = useState([])
const dispatch = useDispatch();
useEffect(() => {
return () => {
console.log("cleaned up");
dispatch(resetMenuContext())
setMenu([])
};
}, []);
useEffect(() => {
if (viewService !== undefined) {
if (viewService?.menuContext !== undefined) {
const { menuContext } = viewService && viewService
setMenu(menuContext)
}
}
}, [viewService])
return (
<>
{
Object.entries(menu).map(([key, value]) => (
<>
<Button
id={`button-${key}`}
aria-controls={open ? `menu-${key}` : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
variant="contained"
disableElevation
onClick={handleClick}
endIcon={<KeyboardArrowDownIcon />}
disableRipple
style={{ textTransform: 'none' }}
>
{i18next.t(`label.${key}`)}
</Button>
<StyledMenu
id={`menu-${key}`}
MenuListProps={{
'aria-labelledby': `button-${key}`,
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
key={key}
>
{
Object.entries(value).map(({ id }) => (
<MenuItem>
{id}
</MenuItem>
))
}
</StyledMenu>
</>
))
}
</>
)
}
what changes i need to make, to display menu i items for each menu.
Value seems to be an array
{
value?.map({ id, label }) => (
<MenuItem key={id}>
{label}
</MenuItem>
))
}
Trying to get an an onclick function to delete an item (that's been clicked) from an array.
However, it doesnt delete anything. setListOfDocs is supposed to set the listOfDocs array with the clicked item gone (onClick function is a filter)
It does not work. There's no error in the console. I don't know why it's not updating the state so that the listOfDocs is the new filtered array (with the clicked item removed from the array).
Im using material ui.
function NewMatterDocuments() {
const [listOfDocs, setListOfDocs] = useState(['item1', 'item2', 'item3', 'item4']);
const [disableButton, toggleDisableButton] = useState(false);
const [displayTextField, toggleDisplayTextField] = useState('none');
const [textInputValue, setTextInputValue] = useState();
const buttonClick = () => {
toggleDisableButton(true);
toggleDisplayTextField('box');
};
//this function works
const handleEnter = ({ target }) => {
setTextInputValue(target.value);
const newItem = target.value;
setListOfDocs((prev) => {
return [...prev, newItem];
});
setTextInputValue(null);
}; //
//this function does not work....which is weird because it pretty much
// does the same thing as the previous function except it deletes an item
// instead of adding it to the array. Why does the previous function work
// but this one doesnt?
const deleteItem = ({ currentTarget }) => {
const deletedId = currentTarget.id;
const result = listOfDocs.filter((item, index) => index !== deletedId);
setListOfDocs(result)
};
return (
<div>
<Typography variant="body2">
<FormGroup>
<Grid container spacing={3}>
<Grid item xs={6}>
<Grid item xs={6}>
Document Type
</Grid>
{listOfDocs.map((item, index) => {
return (
<ListItem id={index} secondaryAction={<Checkbox />}>
<ListItemAvatar>
<Avatar>
<DeleteIcon id={index} onClick={deleteItem} />
</Avatar>
</ListItemAvatar>
<ListItemButton>
<ListItemText id={index} primary={item} />
</ListItemButton>
</ListItem>
);
})}
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Button disabled={!disableButton ? false : true} color="inherit" onClick={buttonClick}>
Add
</Button>
<ListItem sx={{ display: `${displayTextField}` }} id={uuid()} key={uuid()}>
<TextField
id="standard-basic"
label="Additional Document"
variant="standard"
value={textInputValue}
onKeyDown={(e) => {
e.key === 'Enter' && handleEnter(e);
}}
></TextField>
</ListItem>
</List>
</Grid>
</Grid>
</FormGroup>
</Typography>
</div>
);
}
export default NewMatterDocuments;
I tried to change the function that wasnt working into the following:
const deleteItem = ({ currentTarget }) => {
setListOfDocs((prev) => {
prev.filter((item, index) => index != currentTarget.id);
});
};
It gives the error
"Uncaught TypeError: Cannot read properties of undefined (reading 'map')"
The next map function inside the jsx doesnt work...
You should return your data after filter:
const deleteItem = ({ currentTarget }) => {
setListOfDocs((prev) => {
return prev.filter((item, index) => index != currentTarget.id);
});
};
Update:
There is a problem with your first try. The currentTarget.id field is string but in your filter method, you're comparing it with the index (which is number) with !== which also checks types of the 2 variables.
So you can fix it by replacing !== with != or converting string to number.
I believe that you made a small mistake in your filtering function: you are comparing item index with deletedId, where you should compare item with deletedId
This should be correct:
const deleteItem = ({ currentTarget }) => {
const deletedId = currentTarget.id;
const result = listOfDocs.filter((item, index) => item !== deletedId);
setListOfDocs(result)
};
I'm using Material UI's nested/select ItemList component to dynamically generates any number of dropdown menu items based on how many items belong in this header as you can maybe tell from the mapping function. On another file 1 layer above this one, I am again mapping and generating multiple of these DropDownMenus, is it possible for these components to communicate to each other?
This is the file in question
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
maxWidth: 330,
backgroundColor: theme.palette.background.paper,
},
nested: {
paddingLeft: theme.spacing(4),
}
}));
export default function DropDownMenu(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(true);
let unitName = props.unit[0];
let chapterList = props.unit.slice(1);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const handleListItemClick = (index) => {
console.log("ItemClicked");
console.log(index);
setSelectedIndex(index);
};
const handleClick = () => {
setOpen(!open);
};
const selectMenuItem = (chapter, index) => {
props.chooseChapter(chapter)
handleListItemClick(index)
}
let dropDownUnit = chapterList.map((chapter, index) => {
return (
<ListItem button
className={classes.selected}
selected={selectedIndex === index}
onClick={() => selectMenuItem(chapter, index)}
key={index}>
<ListItemText primary={chapter} />
</ListItem>
)
})
return (
<List
component="nav"
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
</ListSubheader>
}
className={classes.root}
>
<ListItem button onClick={handleClick}>
<ListItemText primary={unitName} />
{!open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={!open} timeout="auto" unmountOnExit>
<List component="div" disablePadding className={classes.selected}>
{dropDownUnit}
</List>
</Collapse>
</List>
);
}
Psudo Style - What I'm trying to accomplish
<DropDownMenu>
<MenuItem> // Suppose this is selected
<MenuItem>
<DropDownMenu>
<MenuItem> // onClick --> Select this and deselect all other selected buttons
You can have a parent of these components such that the parent will keep the state of who is active. That way you can pass that state & the state setter as props so that everyone will know who is active
export default function App() {
const [selectedItem, setSelectedItem] = React.useState();
return (
<>
<DropDownMenu
selectedItem={selectedItem} // pass down as props
setSelectedItem={setSelectedItem} // pass down as props
unit={...}
chooseChapter={function () {}}
/>
...
On the children, simply refactor the Call To Action (in this case onClick) to set the state using the passed down props. Pay attention to the selected prop of ListItem, we now use the state we have passed down from the parent
let dropDownUnit = chapterList.map((chapter, index) => {
return (
<ListItem
button
className={classes.selected}
selected={props.selectedItem === chapter}
onClick={() => props.setSelectedItem(chapter)}
key={index}
>
<ListItemText primary={chapter} />
</ListItem>
);
});
I have an array of items and for each item, I want to display a Card component. Each Card has a pop up Menu. I am having trouble opening just the specific clicked Menu to open. My code opens all Menus together. Here is the code snippet.
Second issue is that I get a warning about not being able to having a button within a button. I make the Card Header clickable, and then I have the Menu. What's the correct way to implement this in order to avoid the warning?
const [anchorEl, setAnchorEl] = useState(null)
const handleMenuClick = (e) => {
e.stopPropagation()
setAnchorEl(e.currentTarget)
}
return (
{
props.items.map( (k, i) => (
<Card className={classes.root}>
<CardActionArea onClick={(e) => handleRedirect(e)}>
<MyMenu key={i} index={i} anchor={anchorEl} />
<CardHeader
action={
<IconButton id={i} aria-label="settings" onClick={handleMenuClick}>
<MoreVertIcon />
</IconButton>
}
title={k.title}
subheader={getTimestamp(k._id)}
/>
</CardActionArea>
MyMenu code:
const MyMenu = ( { index, anchor } ) => {
const [anchorEl, setAnchorEl] = useState({})
useEffect(() => {
//setAnchorEl({[e.target.id]: anchor})
if (anchor!==null) {
if (index===anchor.id)
setAnchorEl({[index]: anchor})
}
}, [anchor, index])
const handleRedirect = (e) => {
e.stopPropagation()
//history.push('/item/'+ id)
}
const handleClose = (e) => {
e.stopPropagation()
setAnchorEl({[e.target.id]: null})
};
return (
<Menu
id={index}
anchorEl={anchorEl[index]}
open={Boolean(anchorEl[index])}
onClose={handleClose}
>
<MenuItem onClick={(e) => handleRedirect(e)}>Read</MenuItem>
<MenuItem onClick={(e) => handleRedirect(e)}>Edit</MenuItem>
</Menu>
)
}
You may try below.
const handleMenuClick = (e, setter) => {
e.stopPropagation()
setter(e.currentTarget)
}
return (
{
props.items.map( (k, i) => {
const [anchorEl, setAnchorEl] = useState(null)
return (
<Card className={classes.root}>
<CardActionArea onClick={(e) => handleRedirect(e)}>
<MyMenu key={i} index={i} anchor={anchorEl} />
<CardHeader
action={
<IconButton id={i} aria-label="settings" onClick={(e) => handleMenuClick(e, setAnchorEl)}>
<MoreVertIcon />
</IconButton>
}
title={k.title}
subheader={getTimestamp(k._id)}
/>
</CardActionArea>
</Card>
)
Basically in above, you are creating separate state for each mapped object. And I tweaked the click event to accept a setState callback.
Hope it helps.
Thanks #Sandy. I was able to resolve this by moving this code to the parent component
const [anchorEl, setAnchorEl] = useState({})
const handleMenuClick = (e) => {
e.stopPropagation()
setAnchorEl({[e.currentTarget.id]: e.currentTarget})
}
...
<IconButton id={i} onClick={handleMenuClick}>
I have 2 onClick functions
function VisitGallery(name) {
const history = useHistory();
console.log("visitgallery", name)
history.push("/gallery")
}
function App() {
const accesstoken = "******************"
const [viewport, setviewport] = React.useState({
latitude: ******
longitude: *******
width: "100vw",
height: "100vh",
zoom: 11
})
const [details, setdetails] = React.useState([
])
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore()
const data = await db.collection("data").get()
setdetails(data.docs.map(doc => doc.data()))
}
fetchData();
}, [])
const [selectedpark, useselectedpark] = React.useState(null);
React.useEffect(() => {
const listener = e => {
if (e.key === "Escape") {
useselectedpark(null);
}
};
window.addEventListener("keydown", listener)
return () => {
window.removeEventListener("keydown", listener)
}
}, [])
return (
<div className="App">
<ReactMapGl {...viewport}
mapboxApiAccessToken={accesstoken}
mapStyle="mapbox://**************"
onViewportChange={viewport => {
setviewport(viewport)
}}>
{details.map((details) =>
<Marker key={details.name} latitude={details.lat} longitude={details.long}>
<button class="marker-btn" onClick={(e) => {
e.preventDefault();
useselectedpark(details);
}}>
<img src={icon} alt="icon" className="navbar-brand" />
</button>
</Marker>
)}
{selectedpark ?
(<Popup
latitude={selectedpark.lat}
longitude={selectedpark.long}
onClose={() => {
useselectedpark(null);
}}
>
<div>
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{selectedpark.name}</Card.Title>
<Card.Text>
{selectedpark.postalcode}
</Card.Text>
<Button variant="primary" onClick = VisitGallery() >Visit Gallery</Button>
</Card.Body>
</Card>
</div>
</Popup>)
: null}
{
console.log("in render", details)
}
</ReactMapGl>
</div>
);
}
export default App;
The outer onClick is assigned when the marker is first created, and when it is clicked the useselectedpark function is called, details is then assigned to selectedpark.
The inner onClick is assigned to the function VisitGallery(). When the inner onClick is triggered, i want to navigate to another page, hence the history.push().
Ideally, what i want for it to happen is, when the outer onClick is triggered, the cardview shows, and i have an option to visit the next page, which can be triggered by an onClick within the card. However, what is happening right now is both the onClicks are triggered when i click on the thumbnail. How do i fix it such that it is how i want it to be ideally?
ps: do let me know if my explanation is confusing and i will edit it accordingly
Try adding your second onClick into a callback function?
<Button variant="primary" onClick='()=>{ VisitGallery() }' >Visit Gallery</Button>
So that it doesn't automatically invoke the function until the click is triggered.