I'm using react-sidebar-pro for my sidebar component in my react app and I want my sidebar menu items' active state to turn true when the user has clicked and is redirected to a link. So far what I've done is set a parameter if the user is in a specific address it will turn true but it only updates when I refresh the page. How do I update it without having to refresh the page so it would look responsive? I thought of using useState to resolve this but I have no idea on how to apply it to the code because I'm still learning React
Here's my Sidebar.js
function Sidebar() {
//create initial menuCollapse state using useState hook
const [menuCollapse, setMenuCollapse] = useState(false)
//create a custom function that will change menucollapse state from false to true and true to false
const menuIconClick = () => {
//condition checking to change state from true to false and vice versa
menuCollapse ? setMenuCollapse(false) : setMenuCollapse(true);
}
return (
<>
<div id="header">
{/* collapsed props to change menu size using menucollapse state */}
<ProSidebar collapsed={menuCollapse}>
<SidebarHeader>
<div className="logotext">
{/* small and big change using menucollapse state */}
<p>{menuCollapse ? "LOGO" : "LONG LOGO"}</p>
</div>
<div className="closemenu" onClick={menuIconClick}>
{/* changing menu collapse icon on click */}
{menuCollapse ? (
<FiArrowRightCircle/>
) : (
<FiArrowLeftCircle/>
)}
</div>
</SidebarHeader>
<SidebarContent>
<Menu iconShape="square">
<MenuItem active={window.location.pathname === "/"} icon={<FiHome />} >
Home
<Link to="/"/>
</MenuItem>
<MenuItem active={window.location.pathname === "/FAQ"} icon={<FaList />}>
FAQ
<Link to="/FAQ"/>
</MenuItem>
<MenuItem active={window.location.pathname === "/Search-sec"} icon={<RiPencilLine />}>SEARCH SEC<Link to="/Search-sec"/></MenuItem>
</SidebarContent>
<SidebarFooter>
<Menu iconShape="square">
<MenuItem icon={<FiLogOut />}>Logout</MenuItem>
</Menu>
</SidebarFooter>
</ProSidebar>
</div>
</>
)
}
export default Sidebar
Basically two options here:
make window.location.path as your local state
please refer to Why useEffect doesn't run on window.location.pathname changes?
add another state (changed based on click), to force component re-render
function Sidebar() {
const [menuCollapse, setMenuCollapse] = useState(false);
const [activeIndex, setActiveIndex] = useState(() => {
const initialIndex =
window.location.pathname === '/' ? 0
: window.location.pathname === '/FAQ' ? 1
: window.location. pathname === 'Search-sec' ? 2
: 0;
return initialIndex;
});
const menuIconClick = () => {
setMenuCollapse(!menuCollapse);
};
return (
<>
<div id="header">
{/* collapsed props to change menu size using menucollapse state */}
<ProSidebar collapsed={menuCollapse}>
<SidebarHeader>
<div className="logotext">
{/* small and big change using menucollapse state */}
<p>{menuCollapse ? "LOGO" : "LONG LOGO"}</p>
</div>
<div className="closemenu" onClick={menuIconClick}>
{/* changing menu collapse icon on click */}
{menuCollapse ? (
<FiArrowRightCircle />
) : (
<FiArrowLeftCircle />
)}
</div>
</SidebarHeader>
<SidebarContent>
<Menu iconShape="square">
<MenuItem active={activeIndex === 0} icon={<FiHome />} >
Home
<Link id="MenuItemHome" to="/" onClick={() => setActiveIndex(0)} />
</MenuItem>
<MenuItem active={activeIndex === 1} icon={<FaList />} >
FAQ
<Link id="MenuItemFAQ" to="/FAQ" onClick={() => setActiveIndex(1)} / >
</MenuItem>
<MenuItem active={activeIndex === 2} icon={<RiPencilLine />}>
SEARCH SEC
<Link id="MenuItemSearch" to="/Search-sec" onClick={() => setActiveIndex(2)} />
</MenuItem>
</Menu>
</SidebarContent>
<SidebarFooter>
<Menu iconShape="square">
<MenuItem icon={<FiLogOut />}>Logout</MenuItem>
</Menu>
</SidebarFooter>
</ProSidebar>
</div>
</>
);
}
export default Sidebar;
Related
I'm using this excellent example (Nested sidebar menu with material ui and Reactjs) to build a dynamic nested menu for my application. On top of that I'm trying to go one step further and put it into a Material UI appbar/temporary drawer. What I'd like to achieve is closing the drawer when the user clicks on one of the lowest level item (SingleLevel) however I'm having a tough time passing the toggleDrawer function down to the menu. When I handle the click at SingleLevel I consistently get a 'toggle is not a function' error.
I'm relatively new to this so I'm sure it's something easy and obvious. Many thanks for any answers/comments.
EDIT: Here's a sandbox link
https://codesandbox.io/s/temporarydrawer-material-demo-forked-v11ur
Code is as follows:
Appbar.js
export default function AppBar(props) {
const [drawerstate, setDrawerstate] = React.useState(false);
const toggleDrawer = (state, isopen) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setDrawerstate({ ...state, left: isopen });
};
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static" color="secondary">
<Toolbar>
<IconButton
size="large"
edge="start"
color="primary"
aria-label="menu"
onClick={toggleDrawer('left', true)}
>
<MenuIcon />
</IconButton>
<img src={logo} alt="logo" />
</Toolbar>
<Drawer
anchor='left'
open={drawerstate['left']}
onClose={toggleDrawer('left', false)}
>
<Box>
<AppMenu toggleDrawer={toggleDrawer} />
</Box>
</Drawer>
</AppBar>
</Box >
)
}
Menu.js
export default function AppMenu(props) {
return MenuItemsJSON.map((item, key) => <MenuItem key={key} item={item} toggleDrawer={props.toggleDrawer} />);
}
const MenuItem = ({ item, toggleDrawer }) => {
const MenuComponent = hasChildren(item) ? MultiLevel : SingleLevel;
return <MenuComponent item={item} toggleDrawer={toggleDrawer} />;
};
const SingleLevel = ({ item, toggleDrawer }) => {
const [toggle, setToggle] = React.useState(toggleDrawer);
return (
<ListItem button onClick={() => { toggle('left', false) }}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item }) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} secondary={item.description} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
You shouldn't call a react hook inside of any function that is not a react component. Please see React Rules of Hooks
What you could do instead is pass setToggle directly into the Drawer component as a prop and do something like this for it's onClick attribute:
onClick={() => setToggle(<value>)}
How do I put a button inside the Tab? If I'll click Homepage, it does go in the correct tab with the correct line indicator. However, if I'll click "Profile", the indicator will go to the log out button. How do I correct this where the indicator will stay on the correct tab? This is what it looks like if I'll go to the Profile Tab, the indicator will directly go to the Logout button.
const Header = (props) => {
const { currentUser } = props;
const [value, setValue] = React.useState(0);
const [open, setOpen] = React.useState(false);
const handleChange = (event, newValue) => {
setValue(newValue);
};
// for the tab to stay on the correct path/page even if it was reloaded
useEffect(() => {
let path = window.location.pathname;
if (path === "/" && value !== 0) setValue(0);
else if (path === "/login" && value !== 1) setValue(1);
else if (path === "/registration" && value !== 2) setValue(2);
else if (path === "/profile" && value !== 2) setValue(2);
}, [value]);
const isMatch = useMediaQuery(theme.breakpoints.down("md"));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<AppBar>
{/* */}
<Toolbar
variant="dense">
{isMatch ? (
<h1>
<div>
<MobileviewComponent />
</div>
</h1>
) : (
<div>
<Grid>
<Tabs
value={value}
onChange={handleChange}
>
{currentUser && (
<Tab
disableRipple
label="Homepage"
to="/"
component={Link}
/>
)}
{currentUser && (
<Tab
disableRipple
label="Profile"
to="/profile"
component={Link}
/>
)}
{currentUser && (
<Button color="inherit" onClick={handleClickOpen}>
Logout
</Button>
)}
{!currentUser && (
<Tab
disableRipple
label="Homepage"
to="/"
component={Link}
/>
)}
{!currentUser && (
<Tab
disableRipple
label="Login"
to="/login"
component={Link}
/>
)}
</Tabs>
</Grid>
</div>
)}
</Toolbar>
</AppBar>
</div>
);
};
you are setting state as 2 for both /registration and /profile so when you click on /profile tab value is set to 2 and I guess you are material-ui so the tabs count starts from 0, hence 2 indicates 3rd item in tabs and that is your logout button, hence it is highlighted
React JS - Material UI ListItem (with Collapse API) onClick expand only one at a time close other. on click i need to open only one item at time. and when i click other the present opened should close.requirement is sidenavbar when i open click any menu , one should open . remaining or others should close
const isExpandable = items && items.length > 0;
const [open, setOpen] = useState(false);
const [menu, setMenu] = useState(false);
// const { value, setValue } = useContext(SiderBarContext)
const { expand, setExpand } = useContext(SiderBarContext)
function handleClick() {
// setOpen(!open);
setMenu(!menu)
setOpen(prevState => ({
open: !prevState
}))
// setValue(!open);
setExpand(true)
}
const MenuItemRoot = (
<>
{isExpandable ? <AppMenuItemComponent
className={isExpandable && menu ? classes.menuItemActiveMore : classes.menuItemActiveLess}
link={link}
onClick={handleClick}
>
{/* Display an icon if any */}
{!!Icon && (
<ListItemIcon className={classes.menuItemIcon}>
{<Icon />}
</ListItemIcon>
)}
<ListItemText primary={name} inset={!Icon} />
{/* Display the expand menu if the item has children */}
{isExpandable && !open && <ArrowDropDownIcon />}
{isExpandable && open && <ArrowDropDownIcon />}
</AppMenuItemComponent> : <AppMenuItemComponent
className={classes.menuItem}
link={link}
onClick={handleClick}
>
{/* Display an icon if any */}
{!!Icon && (
<ListItemIcon className={classes.menuItemIcon}>
{/* <Avatar alt="Remy Sharp" src={Icon} className={classes.iconImage}/> */}
{<Icon />}
</ListItemIcon>
)}
<ListItemText primary={name} inset={!Icon} />
{/* Display the expand menu if the item has children */}
{isExpandable && !open && <ArrowDropDownIcon />}
{isExpandable && open && <ArrowDropDownIcon />}
</AppMenuItemComponent>}
</>
);
const MenuItemChildren = isExpandable ? (
<Collapse className={classes.menuChildItem} in={open} timeout="auto" unmountOnExit>
{/* <Divider /> */}
<List component="div" disablePadding >
{items.map((item, index) => (
<>
<AppMenuItem {...item} key={index} />
</>
))}
</List>
</Collapse>
) : null;
return (
<>
{MenuItemRoot}
{MenuItemChildren}
</>
);
};
I'm trying to learn react and fairly new to the framework. I am trying to create a simple navbar component wih material-ui that is responsive (will show all links on medium devices and up, and open a side drawer on small devices). I have most of it setup to my liking, however, the issue I am currently having, is getting and setting the active link according to the page I am on.
It seems to works correctly on the medium devices and up, but when transitioning to a smaller device, the link is not updated correctly, as it will keep the active link from the medium screen set, while updating the side drawer active link.
Navbar.js
const Navbar = () => {
const classes = useStyles();
const pathname = window.location.pathname;
const path = pathname === '' ? '' : pathname.substr(1);
const [selectedItem, setSelectedItem] = useState(path);
const handleItemClick = (event, selected) => {
setSelectedItem(selected);
console.log(selectedItem);
};
return (
<>
<HideNavOnScroll>
<AppBar position="fixed">
<Toolbar component="nav" className={classes.navbar}>
<Container maxWidth="lg" className={classes.navbarDisplayFlex}>
<List>
<ListItem
button
component={RouterLink}
to="/"
selected={selectedItem === ''}
onClick={event => handleItemClick(event, '')}
>
<ListItemText className={classes.item} primary="Home" />
</ListItem>
</List>
<Hidden smDown>
<List
component="nav"
aria-labelledby="main navigation"
className={classes.navListDisplayFlex}
>
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === 'account/login'}
onClick={event => handleItemClick(event, 'account/login')}
>
<ListItemText className={classes.item} primary="Login" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/account/register"
selected={selectedItem === 'account/register'}
onClick={event => handleItemClick(event, 'account/register')}
>
<ListItemText className={classes.item} primary="Register" />
</ListItem>
</List>
</Hidden>
<Hidden mdUp>
<SideDrawer />
</Hidden>
</Container>
</Toolbar>
</AppBar>
</HideNavOnScroll>
<Toolbar id="scroll-to-top-anchor" />
<ScrollToTop>
<Fab aria-label="Scroll back to top">
<NavigationIcon />
</Fab>
</ScrollToTop>
</>
)
}
SideDrawer.js
const SideDrawer = () => {
const classes = useStyles();
const [state, setState] = useState({ right: false });
const pathname = window.location.pathname;
const path = pathname === "" ? "" : pathname.substr(1);
const [selectedItem, setSelectedItem] = useState(path);
const handleItemClick = (event, selected) => {
setSelectedItem(selected);
console.log(selectedItem);
};
const toggleDrawer = (anchor, open) => (event) => {
if (
event &&
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setState({ ...state, [anchor]: open });
};
const drawerList = (anchor) => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List component="nav">
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === "account/login"}
onClick={(event) => handleItemClick(event, "account/login")}
>
<ListItemText className={classes.item} primary="Login" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === "account/register"}
onClick={(event) => handleItemClick(event, "account/register")}
>
<ListItemText className={classes.item} primary="Register" />
</ListItem>
</List>
</div>
);
return (
<React.Fragment>
<IconButton
edge="start"
aria-label="Menu"
onClick={toggleDrawer("right", true)}
>
<Menu fontSize="large" style={{ color: "white" }} />
</IconButton>
<Drawer
anchor="right"
open={state.right}
onClose={toggleDrawer("right", false)}
>
{drawerList("right")}
</Drawer>
</React.Fragment>
);
};
Code Sandbox - https://codesandbox.io/s/async-water-yx90j
I came across this question on SO: Is it possible to share states between components using the useState() hook in React?, which suggests that I need to lift the state up to a common ancestor component, but I don't quite understand how to apply this in my situation.
I would suggest to put aside for a moment your code and do a playground for this lifting state comprehension. Lifting state is the basic strategy to share state between unrelated components. Basically at some common ancestor is where the state and setState will live. there you can pass down as props to its children:
const Parent = () => {
const [name, setName] = useState('joe')
return (
<>
<h1>Parent Component</h1>
<p>Child Name is {name}</p>
<FirstChild name={name} setName={setName} />
<SecondChild name={name} setName={setName} />
</>
)
}
const FirstChild = ({name, setName}) => {
return (
<>
<h2>First Child Component</h2>
<p>Are you sure child is {name}?</p>
<button onClick={() => setName('Mary')}>My Name is Mary</button>
</>
)
}
const SecondChild = ({name, setName}) => {
return (
<>
<h2>Second Child Component</h2>
<p>Are you sure child is {name}?</p>
<button onClick={() => setName('Joe')}>My Name is Joe</button>
</>
)
}
As you can see, there is one state only, one source of truth. State is located at Parent and it passes down to its children. Now, sometimes it can be troublesome if you need your state to be located at some far GreatGrandParent. You would have to pass down each child until get there, which is annoying. if you found yourself in this situation you can use React Context API. And, for most complicated state management, there are solutions like redux.
const handleMenuItem = (e, name, addr) => {
e.stopPropagation();
console.log(addr);
}
{Object.values(addressList.address).length &&
addressList.address.map((a) => (
<Grid item sm={4} key={a.addrId}>
<Card>
{showCheckIcon[a.addrId] ? (
<IconButton className="my-address-checked">
<CheckCircleIcon />
</IconButton>
) : (
<React.Fragment>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
className="my-address-verticon"
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
>
{options.map((option) => {
console.log(a);
return (
<MenuItem
key={option.name}
onClick={(e) => handleMenuItem(e, option.name, a)}
>
{option.icon}
{option.name}
</MenuItem>
);
})}
</Menu>
</React.Fragment>
)}
<StepLabel className="text-left">
<h4>{a.addrCustName}</h4>
<p>{a.addrAddress}</p>
<p>{`${a.addrCity}, ${a.addrPincode}`}</p>
<p>{`${a.addrState}, ${a.addrCountry}`}</p>
<p>{a.addrCustPhone}</p>
</StepLabel>
</Card>
</Grid>
))}
I noticed when my handleMenuItem handler is called, it is getting the incorrect data. I believe it is getting the last data as the loop is finished by the time. Basically, what handleMenuItem does is, it selects the address for delivery based on the click event, however, it is selecting the next address when the click event is fired. What is that i could do to make it work as expected ?
Issue
When a user clicks on the More-Menu IconButton, the onClick handler assigns the current target to anchorEl. There is only one of these, so when mapping the address the Menu anchorEl prop of all mapped elements is set to the same target, i.e. the last one wins out.
Partial Solution
I say this is a partial solution as it only addresses the exhibited behavior of "selecting the last element" of the address array. You'll need to massage the anchorEl references to get the popup to locate correctly.
Add a selected id state value
const [selectedAddrId, setSelectedAddrId] = useState(null);
Remove const open = Boolean(anchorEl)
Test the open condition if the current address' addrId matches
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={a.addrId === selectedAddrId}
onClose={handleClose}
>