Problem with reseting openKeys in Ant Design Menu within Sider - reactjs

I have antd Menu inside collapsible Sider. I've set default open keys for one of Submenus and they should open one at a time. Here is my code for the Menu:
const MainMenu = ({ defaultOpenKeys }) => {
const [openKeys, setOpenKeys] = useState(defaultOpenKeys);
const rootKeys = ["sub1", "sub2"];
// Open only one submenu at a time
const onOpenChange = props => {
const latestOpenKey = props.find(key => openKeys.indexOf(key) === -1);
if (rootKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(props);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : defaultOpenKeys);
}
};
return (
<Menu
theme="dark"
openKeys={openKeys}
defaultSelectedKeys={["1"]}
mode="inline"
onOpenChange={onOpenChange}
>
<Menu.Item key="1">
Option 1
</Menu.Item>
<SubMenu key="sub1" title="User">
<Menu.Item key="2">Tom</Menu.Item>
</SubMenu>
<SubMenu key="sub2" title="Team">
<Menu.Item key="3">Team 1</Menu.Item>
</SubMenu>
</Menu>
);
};
export default MainMenu;
I pass defaultOpenKeys from the Sider.
const SiderDemo = () => {
const [collapsed, setCollapsed] = useState(false);
const toggleSider = () => {
setCollapsed(!collapsed);
};
return (
<Layout style={{ minHeight: "100vh" }}>
<Button type="primary" onClick={toggleSider}>
{React.createElement(
collapsed ? MenuFoldOutlined : MenuUnfoldOutlined
)}
</Button>
<Sider
collapsible
collapsed={collapsed}
collapsedWidth={0}
trigger={null}
>
<Menu defaultOpenKeys={["sub1"]} />
</Sider>
...
</Layout>
);
};
It works on mount, but when I collapse the Sider, defaultOpenKeys are being reset. How can I keep defaultOpenKeys from being reset, when the Sider is collapsed?
I have created a codesandbox and added console log in the Menu. You can see that defaultOpenKeys and openKeys are the same on mount. When I collapse the Sider, the console log is triggered twice. The first time defaultOpenKeys and openKeys are the same. And the second time openKeys become empty. How can I fix that?

Reason: on closing the sidebar it is closing opened sidemenu so it gonna trigger openchange with empty array and hence your logic making it reset to empty.
Here is code sandbox link with updated code
https://codesandbox.io/s/sider-demo-0der5?file=/Menu.jsx
Suggestion: Its anti pattern to assign props to initial state. if prop value changed in parent component then the new prop value will never be displayed because intial state will never update the current state of the component. The initialization of state from props only runs when the component is first created

Related

All accordions collapsed at the same time when I click on any one

I am creating accordions with reactstrap but when I wanna open one accordion all accordions collapsed
I wanna every accordion work separately
my code that I created 2 components in it
const SellerShipping = () => {
const [open, setOpen] = useState(null);
const [toggleChecked, setToggleChecked] = useState(false)
return (
<Wrapper>
<TagLine>Shipping and delivery</TagLine>
<SearchBar>
<SearchBox />
<SuggestButton>suggest a company</SuggestButton>
</SearchBar>
<AccordionWrapper className=''>
<CompanyAccordion toggleChecked={toggleChecked} setToggleChecked={setToggleChecked} open={open} setOpen={setOpen} toggleid={"1"} />
<CompanyAccordion toggleChecked={toggleChecked} setToggleChecked={setToggleChecked} open={open} setOpen={setOpen} toggleid={"2"} />
</AccordionWrapper>
</Wrapper>
)
}
and even if I write states inside the accordion file its the same result
the company accordion
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from 'reactstrap';
import styled from 'styled-components';
import ReactSwitch from 'react-switch';
import { ReactComponent as EmiratesPost } from 'assets/img/logos/emirates-post.svg'
const CompanyAccordion = ({open, setOpen, toggleid, toggleChecked, setToggleChecked}) => {
const toggle = (id) => {
open === id ? setOpen() : setOpen(id);
console.log(id,open);
};
return (
<Wrapper>
<SubWrapper>
<EmiratesPost style={{maxWidth:"100%"}} />
<SwitchComponent>
<ReactSwitch onChange={()=>setToggleChecked(!toggleChecked)} checked={toggleChecked}/>
</SwitchComponent>
</SubWrapper>
<AccordionWrapper>
<Accordion open={open} toggle={toggle}>
<AccordionItem style={{border:"0px"}} >
<AccordionHeader targetId={toggleid}>
Accordion Item 1
</AccordionHeader>
<AccordionBody accordionId={toggleid}>
<strong>This is the first item's accordion body.</strong>
You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
</AccordionBody>
</AccordionItem >
</Accordion>
</AccordionWrapper>
</Wrapper>
);
}
the result
the result
you have used the same state toggleChecked for all Accordions so they are behaving identically use different states of all the Accordions you wish to have separate behaviour

Function call and pass value in the same OnClick - React

I got a OnClick which actually receives an id:
<Button onClick={() => addToCart(id)} >Buy</Button>
On the other hand, in a different JS file,I got a modal which appears with a click via useState:
const [stateModal1, changeModalState1] = useState(false);
Now, in the same component I work with this modal, I map an array which returns a Button, which now is working with the "addToCart(id)" value mentioned before, like this:
{products.map((product) => {
return <Product image={product.image}
key={product.id}
data={product}
addToCart={() =>addToCart(product.id)} />})}
The question that is driving me crazy is: how can I use the button in the mapped array to trigger that modal, and at the same time, to pass values to that modal in order to show the mapped item IN that modal?
Thanks in advance.
EDIT: this is the modal, which is another component:
const Modal = ({
children,
state,
stateModal1,
})
return (
<>
{state &&
<Overlay>
<Container>
<CloseButton onClick={() => changeState(false)}>{FaWindowClose}</CloseButton >
{children}
<Header>
<h3>Confirm buy</h3>
<h4>{name}</h4>
<h4>$ {price}</h4>
</Header>
<Button onClick={() => changeState(false)}>Confirm</Button>
</Container>
</Overlay>
}
</>)
PS: the "confirm" button which triggers the "changeState()", should also trigger the addToCart().
As mentioned by other comments above, you can pass a prop to the modal component from the parent component to achieve your demand normally.
The only thing that needs to be done is set the open/close modal state and the passing data state at the same time, or, probably use one state directly
sample of the code:
import "./styles.css";
import "antd/dist/antd.css";
import { useState } from "react";
import { Modal } from "antd";
export default function App() {
// init with undefined, if not undefined, open the modal
const [modal, setModal] = useState(undefined);
const list = [...Array(20).keys()];
// set the state to open the modal, as well as pass it to the modal itself as a prop if necessary
const handleClick = (idx) => () => {
setModal(idx);
};
return (
<div className="App">
{list.map((x, idx) => (
<div style={{ border: "1px solid black" }} onClick={handleClick(idx)}>
{x}
</div>
))}
<Modal visible={modal !== undefined}>The value you passed: {modal}</Modal>
</div>
);
}
the online demo could be found here: https://codesandbox.io/s/hardcore-shape-89y78?file=/src/App.js

Call a material ui dialog

How to call a Material UI Dialog during onClick on delete icon ie onClick={deletePlayer(id)} ?
I have added the Dialog.js under modal/Dialog and imported to Home component.
I have added a demo here
Short answer: Forked CodeSandbox with working dialog
Long answer:
First of all, you need to move the display/dismiss logic out of the AlertDialog component and into the component that actually triggers the display of the modal (in your case, the Home component). This means that you'll receive the open state and onClose handler as props (along with the playerId which will hold the ID of the player being targeted for deletion). So the signature of your dialog component becomes:
export default function AlertDialog({ open, onClose, playerId }) {
return (
<Dialog open={open} onClose={onClose} ...> ... </Dialog>
);
}
In Home, we add the logic to track and set the state of both the dialog open/closed status, and the ID of the player targeted for deletion. We do this through useState:
const [deleteDialog, setDeleteDialog] = useState(false);
const [playerId, setPlayerId] = useState("");
While you could have as many AlertDialog components as you have players by adding <AlertDialog /> inside your player map loop, it is redundant as you'll only ever have one modal active (by definition). So all you have to do is place a single instance of <AlertDialog /> in your Home component. A good convention is to place it before the closing encompassing tag:
return (
<div className="App">
.
.
.
<AlertDialog
open={deleteDialog}
onClose={() => setDeleteDialog(false)}
playerId={playerId}
/>
</div>
);
Finally, we deal with the handler responsible for displaying the modal, in your case deletePlayer. We have two things to do there: set the player ID targeted for deletion through the playerId state variable, and display the modal through the deleteDialog state variable:
const deletePlayer = id => e => {
setPlayerId(id);
setDeleteDialog(true);
};
Create a state in the Home component to handle the Dialog visibility, set the state on click and render the AlertDialog conditionally:
const [openDialog, setOpenDialog] = useState(false);
...
const deletePlayer = id => e => {
setOpenDialog(true);
};
...
return(
...
{openDialog && (
<AlertDialog isOpen={openDialog} setIsOpen={setOpenDialog} />
)}
Then in the AlertDialog component:
export default function AlertDialog(props) {
const { isOpen, setIsOpen } = props;
const handleClose = () => {
setIsOpen(false);
};
return (
<div>
<Dialog
open={isOpen}
onClose={handleClose}
...
Working Example:

Material UI Snackback reloads the page in Functional component

Why changing the state here in the codesandbox here do not reloads/render/paint the page. I have also added comments where i am not understanding
https://codesandbox.io/s/material-demo-nb5ek?file=/demo.js
I did same thing in my case according to the material UI snackbar documentation: The only difference is that i pass the click function to child and invoke from there.
I have created a material UI snackbar in the parent which is a functional component and pass the notification function in Route shown below to the child which is also a functional component.
const [openNotification, setOpenNotification] = React.useState(false);
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpenNotification(false);
};
const notification = (status) => {
setOpenNotification(status)
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route exact {...rest} render={(props) => (
<Component {...props} notification={notification}/>
)} />
)
This is the HTML where i am using it in the parent.
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={openNotification}
autoHideDuration={6000}
onClose={handleClose}
message='Success'
action={
<React.Fragment>
<IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
}
/>
In child i have used the notification function like this in the submit function.
function handleSubmit() {
props.notification(true)
}
Problem: The page renders when the snackbar closes and i dont want it to happen as it just a notification. I know snackbar have a state related to it which is changing from true to false. Although the example over here do not renders the page again from their documentation sandbox when the snackbar closes: https://codesandbox.io/s/5tgvq.
The link to simple snackbar documentation: https://material-ui.com/components/snackbars/
I tried not to use state for it and just make a variable and update it. But then the snackbar wont open even if the value gets true

How to use checkboxes inside Antd Menus

I would like to add checkboxes inside Antd Submenu. Following is my code.
<Menu style = {{height: '100vh', overflow: 'auto'}} mode="inline" inlineCollapsed = "false">
<SubMenu key="sub1" onTitleClick = {subMenuTitleClick} title={<span><Icon type="mail" onClick = {this.temp}/><Checkbox onClick = {this.checkboxClick}></Checkbox><span>Sources</span></span>}>
<Menu.Item key={key1}>{detail.docTtl}</Menu.Item>
</SubMenu>
</Menu>
Here, click on Submenu should call subMenuTitleClick and click on checkbox should call checkboxClick.
Ok, so I don't have the full scope but I'll try some assumptions :
Do not use whitespeces in properties declarations :
onClick={} : good. onClick = {} : bad
onTitleClick={subMenuTitleClick} is not a click event, so make sure the <SubMenu/> component handles it correctly
Your <Checkbox/> has a onClick={this.temp}: make sure that this.temp is actually a click handler function
Here is an example. I removed a few things to make the whole more readable
class MyClass extends React.Component {
handleWhateverClic = () => { /* Do stuff */ }
handleCheckboxClick = () => { /* Do stuff */ }
render() {
return (
<Menu >
<SubMenu onTitleClick={this.handleWhateverClic} title={
<span>
<Icon type="mail" onClick={this.handleWhateverClic}/>
<Checkbox onClick={this.handleCheckboxClick} />
<span>Sources</span>
</span>
}>
<Menu.Item>{detail.docTtl}</Menu.Item>
</SubMenu>
</Menu>
)
}
}

Resources