React dropdown function triggers all dropdowns instead of selected menu - reactjs

So I'm trying to figure out how to only trigger the dropdown for the selected menu. Right now if I click on any of my menu items, it triggers every single dropdown.
I currently have a simple function that sets the state from false to true
const showSubnav = () => setSubnav(!subnav);
I attempted to use useRef() but for some reason the ref.current kept showing the wrong element that I clicked on.
Here is my current dropdown code
{SidebarData.map((item, index) => {
return (
<>
<li
ref={ref}
// Here's the function that checks if there's a sub menu, then it triggers
showSubnav
onClick={item.subNav && showSubnav}
key={index}
className={item.cName}
>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
{subnav ? (
<>
{item.subNav &&
item.subNav.map((item, index) => {
return (
<div key={index} className='sub-nav-container'>
<Link to={item.path} className={item.cName}>
{item.icon}
<span>{item.title}</span>
</Link>
</div>
);
})}
</>
) : null}
</>
);
})}
So the issue is that any li with a sub menu will display if I click on it using my code
onClick={item.subNav && showSubnav}
I need a function or way to check for the current element clicked and to only trigger that sub menu for that specific element.
I also have react icons that I used in my data file
So I'm trying to display them only if there's a sub nav
This code is the logic, but I can't seem to fit it anywhere properly
if(item.subNavExists) {
{item.downArrow}
} else if(subnav is click) {
{item.upArrow}
}
else {
return null
}
How would I fit this logic inside of my li tags?
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>

Try this approach,
Create a separate component for Sidebar and track each sidebar changes separately using local state like below,
import { Link } from "#material-ui/core";
import React, { useState } from "react";
import "./styles.css";
const SidebarData = [
{
id: 1,
title: "Item1"
},
{
id: 2,
title: "Item2"
}
];
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{SidebarData.map((item, index) => {
return <Sidebar item={item} />;
})}
</div>
);
}
const Sidebar = ({ item }) => {
const [selected, setSelected] = useState(false);
return (
<div class="container">
<button onClick={() => setSelected(!selected)}>
{selected ? "hide" : "show"}
</button>
<li key={item.id} className={item.cName}>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
{selected && <div>SUB-NAV</div>}
</div>
);
};
Sample demo - https://codesandbox.io/s/compassionate-booth-mj9xo?file=/src/App.js

Related

How to close an opened menu with submenu items when another menu is clicked

The Problem
I have a sidebar with menus and some of the menus has submenus. I would like to close any opened menu with submenus when a different menu is clicked.
Stack
Written in React with react hooks
Sidebar
const [open, setOpen] = useState(true);
return (
<div>
<ul>
{SidebarData.map((item, index) => {
return (
<SubMenu
item={item}
key={index}
titleOpen={!open}
dropOpen={open}
subMenuOpen={open}
/>
);
})}
</ul>
</div>
);
}
SubMenu
function SubMenu({ item, titleOpen, dropOpen, subMenuOpen }) {
const [subnav, setSubnav] = useState(false);
const showSubnav = () => setSubnav(!subnav);
return (
<div>
<ul>
<Link to={item.path} onClick={item.subNav && showSubnav}>
<li>
<span>{item.icon}</span>
<span>
{item.title}
</span>
{item.subNav && dropOpen && (
<KeyboardArrowDownIcon/>
)}
</li>
</Link>
{subnav &&
subMenuOpen &&
item.subNav.map((item, index) => {
return (
<ul>
<Link to={item.path} key={index}>
<li>
<div>{item.title}</div>
</li>
</Link>
</ul>
);
})}
</ul>
</div>
);
}
Each menu has a unique id
Link to Sandbox
https://codesandbox.io/s/laughing-flower-x5rwkh
To achieve this you would need to move state of opened menus outside of the Submenu component and consume it as a prop instead.
Sidebar
const [openedMenuIndex, setOpenedMenuIndex] = useState(0);
return (
<div>
<ul>
{SidebarData.map((item, index) => (
<SubMenu
item={item}
key={index}
isOpened={openedMenuIndex === index}
onClickSubnav={() => setOpenedMenuIndex(index)}
/>
)}
</ul>
</div>
);
}
Submenu
function SubMenu({ item, isOpened, onClickSubnav}) {
return (
<div>
<ul>
<Link to={item.path} onClick={item.subNav && onClickSubnav}>
....
// later in the code show/hide subnav based on value in isOpened property
There are probably a couple ways to do it, but I think the easiest would be to have a useState to keep track of the menu item that is expanded:
const [expanded, setExpanded] = React.useState("name of default menu item to expand"); // or React.useState(null) to collapse all by default
and then use that variable when determining whether or not to show something (and how you do it is up to you), e.g.:
<SubMenuItem visible={expanded === 'subitem3'} onChange={setExpanded('subitem3')}>
visible is probably not a real property, but whatever it is that you use as the condition to determine whether the subitem is in an expanded state or not. Changing expanded then re-evaluates all the other submenu item conditions, which will collapse them.

how can I add any event to a specific part of component ? react

I have list of data that render it with map - I need to add an event just in one of the item from that list.
const UserModal = (props) => {
const {user,setUser} = props ;
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat />},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
this my code and for example I need to add an event on specific object that has id=5 in that list .
how can I do that
I don't know if there is some sort of built-in solution for this, but here is a simple workaround:
I changed a few things for simplicity's sake
The important part is the if statement with checks if item ID is 5 then if so adds a div with the desired event
function App() {
const list = [
,
{ id: 3, text: "comonent 3" },
{ id: 5, text: "comonent 5 (target)" }
];
return (
<>
<h1>Hello world<h1/>
{list.map((item) => (
<div key={item.id} style={{ backgroundColor: "red" }}>
<p>{item.text}</p>
{item.id == 5 ? (
<div
onClick={() => {
alert("This component has a event");
}}
>
{" "}
event
</div>
) : (
<></>
)}
</div>
))}
</>
);
}
const UserModal = (props) => {
const {user,setUser} = props ;
const myEvent = () => alert('event fired');
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat /> , event : myEvent},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p onClick={item.event}>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
list.map((item, i)=> (
item.id == 5 ?
<div onClick={handleClick} key={i}></div>
:
<div key={i}></div>
)

Passing value from child to parent, after click event in child (which triggers function)

In <Nav/> component, "click event" on chevron <button>, triggers nextTitle(length) function in useNextTitle.js custom hook. This function sets the value of val, which is being returned by useNextTitle.js. How to pass that new val to App.js ?
Bigger picture: I want to change the display between <Dod /> and <Analogia /> (in App.js), after "click event" in <Nav /> (figured out val would be helpful for that, as a parameter in Conditional Statement).
Functionality I`m trying to achieve is visualized on the website I done with vanilla Java Script : link (the blue navigation, changes "main pages" with "titles" when chevron clicked)
App.js
import Nav from "./components/Nav";
import Dod from "./components/Dod";
import Analogia from "./components/Analogia";
function App() {
return (
<div className="App">
<Nav />
<Dod />
<Analogia />
</div>
);
}
export default App
Nav.js
import useNextTitle from './useNextTitle';
import './Nav.css';
const Nav = () => {
const navData = [
{id: 0, text: "DOD"},
{id: 1, text: "analogia"}
]
const length = navData.length;
const { val, nextTitle } = useNextTitle();
return (
<nav>
<div>
{/* titles */}
<ul>
<li key="li1">
{navData.map((title, index) => {
return (
<div
className={index === val ? "active" : "display-none"} key={title.id}>
{title.text}
</div>
)
})}
</li>
</ul>
{/* chevron button */}
<div>
<button onClick={() => nextTitle(length)}>
<span className="material-icons">
chevron_right
</span>
</button>
</div>
</div>
</nav>
)
}
export default Nav
useNextTitle.js
import { useState } from 'react';
const useNextTitle = () => {
const [val, setVal] = useState(0);
const nextTitle = (length) => {
setVal(val === length -1 ? 0 : val + 1 )
console.log("hook vav = " + val)
}
return { val, nextTitle }
}
export default useNextTitle;
Move the useNextTitle hook/state up to App and pass val and nextTitle down to Nav to toggle/update the state. Use val to conditionally render Dod and Analogia.
Example:
function App() {
const { val, nextTitle } = useNextTitle();
return (
<div className="App">
<Nav {...{ val, nextTitle }} />
{val === 0 && <Dod />}
{val === 1 && <Analogia />}
</div>
);
}
...
const Nav = ({ val, nextTitle }) => {
const navData = [
{ id: 0, text: "DOD" },
{ id: 1, text: "analogia" }
];
const length = navData.length;
return (
<nav>
<div>
{/* titles */}
<ul>
<li key="li1">
{navData.map((title, index) => {
return (
<div
className={index === val ? "active" : "display-none"}
key={title.id}
>
{title.text}
</div>
);
})}
</li>
</ul>
{/* chevron button */}
<div>
<button onClick={() => nextTitle(length)}>
<span className="material-icons">chevron_right</span>
</button>
</div>
</div>
</nav>
);
};

Hiding an element after a while in Next.js

I have a header component as a function component. I want show a popup when logo text is clicked. After for a time it should close automatically. I use hooks for state of popup. But set state function doesn't work in setTimeout function. How can fix this?
import Link from 'next/link'
import style from './header.module.css'
const Header = () => {
const [popupOpen, setPopupOpen] = React.useState(false)
return (
<header className={style.header}>
<nav className={style.nav}>
<div
className={style.popupContainer}
onClick={() => {
setPopupOpen(!popupOpen)
console.log(popupOpen)
setTimeout(() => {
console.log(popupOpen)
setPopupOpen(!popupOpen)
console.log(popupOpen)
}, 1000)
}}
>
<span className={style.logo}>Logo</span>
<span
className={`${style.popupText} ${
popupOpen ? style.show : style.hide
}`}
>
Popup Text
</span>
</div>
<ul className={style.ul}>
<li>
<Link href='/'>
<a>.home</a>
</Link>
</li>
<li>
<Link href='/contact'>
<a>.contact</a>
</Link>
</li>
</ul>
</nav>
</header>
)
}
export default Header
Console log:
Let me suggests, this is the same question as:
React - useState - why setTimeout function does not have latest state value?
const _onClick = () => {
setPopupOpen(!popupOpen);
setTimeout(() => {
setPopupOpen(popupOpen => !popupOpen)
}, 2000);
};
Its happening because setPopupOpen is asynchronous. So by the time setPopupOpen(!popupOpen) is called it has same value as onClick first setPopupOpen(!popupOpen) so eventually when it called both setPopup doing same state update i.e both updating as false. Better way is to usesetPopupOpen callback function to update the value. I added this code.
import { useState } from "react";
import Link from "next/link";
import style from "./style.module.css";
const Header = () => {
const [popupOpen, setPopupOpen] = useState(false);
const toggle = () => {
setPopupOpen((prev) => !prev);
};
const onClick = () => {
setPopupOpen(!popupOpen);
setTimeout(() => {
toggle();
}, 1000);
};
return (
<header className={style.header}>
<nav className={style.nav}>
<div className={style.popupContainer} onClick={onClick}>
<span className={style.logo}>Logo</span>
{popupOpen && (
<span
className={`${style.popupText} ${
popupOpen ? style.show : style.hide
}`}
>
Popup Text
</span>
)}
</div>
<ul className={style.ul}>
<li>
<Link href="/">
<a>.home</a>
</Link>
</li>
<li>
<Link href="/contact">
<a>.contact</a>
</Link>
</li>
</ul>
</nav>
</header>
);
};
export default function IndexPage() {
return (
<div>
<Header />
</div>
);
}
Here is the demo: https://codesandbox.io/s/pedantic-haibt-iqecz?file=/pages/index.js:0-1212

Reset pagination to the first page by clicking a button outside the component

I'm using material UI usePagination hook to create a custom pagination component, so far so good, the functionality works as expected but I was wondering how I can be able to reset the pagination to the first page by triggering a button that is not part of the pagination component.
Does anyone has an idea on how to trigger that?
This is my component.
import React from "react";
import PropTypes from "prop-types";
import { usePagination } from "hooks";
function arrow(type) {
return (
<i
className={`fa fa-chevron-${
type === "next" ? "right" : "left"
} page-icon`}
/>
);
}
function Pagination({ data, itemCount, onChange }) {
const { items } = usePagination({
count: Math.ceil(data.length / itemCount, 10),
onChange
});
return (
<nav aria-label="Paginator">
<ul className="pagination-component">
{items.map(({ page, type, selected, ...item }, index) => {
let children;
if (type === "start-ellipsis" || type === "end-ellipsis") {
children = "…";
} else if (type === "page") {
children = (
<button
type="button"
automation-tag={`page-${page}`}
className={`page-button ${selected ? "selected" : ""}`}
{...item}
>
{page}
</button>
);
} else {
children = (
<button
automation-tag={type}
className="page-button"
type="button"
{...item}
>
<span className="d-none">{type}</span>
{arrow(type)}
</button>
);
}
return (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className="page-item">
{children}
</li>
);
})}
</ul>
</nav>
);
}
What I'm trying is to create a select component that the onChange function will sort the data, depending on the selection, but when the data is sorted I want to return the pagination component to the first page
const TableVizContainer = props => {
const [currentPage, setCurrentPage] = useState(1);
const [sortColumn, setSortColumn] = useState(1);
const [range, setRange] = useState({
start: 0,
end: 25
});
const onChangePage = (_event, page) => {
setCurrentPage(page);
setRange({
start: 25 * (page - 1),
end: 25 * page
});
};
const onSelectChange = event => {
const { value } = event.target;
setCurrentPage(1);
setSortColumn(parseInt(value, 10));
};
return (
<div
className="table-viz-container container-fluid my-4 float-left"
automation-tag={`table-viz-${automationId}`}
>
<div className="d-flex justify-content-between mb-3 leaderboard-meta">
<span className="leaderboard-title">{visualization.title}</span>
<div className="mr-5">
<label htmlFor="sort-table-select">
Sort By:
<select
id="sort-table-select"
onChange={onSelectChange}
value={sortColumn}
>
{visualization.columns.map((column, index) => {
const uniqueId = uuidv1();
return (
<option key={uniqueId} value={index}>
{setSelectValue(column, visualization.metrics)}
</option>
);
})}
</select>
</label>
</div>
</div>
<div className="d-block d-sm-flex justify-content-between align-items-center my-2 px-2">
<span className="page-items-count" automation-tag="pagination-count">
{`Showing ${range.start === 0 ? 1 : range.start + 1} - ${
range.end <= visualization.rows.length
? range.end
: visualization.rows.length
} of ${visualization.rows.length}.`}
</span>
<Pagination
currentPage={currentPage}
data={visualization.rows}
itemCount={25}
onChange={onChangePage}
/>
</div>
</div>
);
};
Does anyone has an idea on how to reset and move the pagination page to the first one without clicking the component?
There are two ways.
1. Passing Props
Let's just say you have a function called jump() and passing 1 as an argument will reset the pagination. So, you can pass the jump function as a property and reuse that on other components.
function jump(){
setCurrentPage(1)
}
<MyCompnent resetPage={jump} />
// MyComponent
function MyComponent({resetPage}){
return (
<button onClick={resetPage(1)}></button>
)
}
2. On Changing Route
You can reset your pagination when your route will change. For example, you are using a router npm package and that package has a method called onChange or routeChangeStart. With those methods or after creating that method you can implement a function like below.
Router.events.on("routeChangeStart", () => {
jump(1);
});

Resources