React list item with icon on Click event - reactjs

On click of list item want to show checked icon in react. Below is list of items.
Now on click of any item need to show check icon to understand this menu is clicked/active like below.
That is need to list item should be present with icon in react js. Below is current code which uis used to list items.
<div key="favorites-dropdown" className={styles.pushBtnDropDown}>
<ul key="favorites-list">
{
favorites.map((favorite, idx) => {
return (<li key={`favorite-${idx}`}>
<a
key={`favorite-a-${idx}`}
tabIndex={idx}
role="button"
onClick={() => store.setFavoriteFilters(favorite.reportName, favorite.filters)}
>
{favorite.name}
</a>
</li>);
})
}
</ul>
</div>
Can someone please help to achieve this output.
Any reference or any another example with code will be helpful.
Thanks in advance.

You can create array favoritesFilters where you will save all checked filter names and render icons with condition favoritesFilters.includes(favorite.name). Example:
import { useState } from "react";
...
const [favoritesFilters, setFavoritesFilters] = useState([]);
const handleFavorites = (favorite) => {
store.setFavoriteFilters(favorite.reportName, favorite.filters);
setFavoritesFilters(prev => {
if(prev.includes(favorite.name)) {
let newPrev = [...prev];
return newPrev.filter(item => item !== favorite.name)
}
return [...prev, favorite.name]
})
}
...
<div key="favorites-dropdown" className={styles.pushBtnDropDown}>
<ul key="favorites-list">
{
favorites.map((favorite, idx) => {
return (<li key={`favorite-${idx}`}>
//your icon
{favoritesFilters.includes(favorite.name) && <img src="your_icon_path" />}
<a
key={`favorite-a-${idx}`}
tabIndex={idx}
role="button"
onClick={() => handleFavorites(favorite)}
>
{favorite.name}
</a>
</li>);
})
}
</ul>
</div>

Related

Active object of the mapped array show center in scroll items react

I have mapped my data into timetable and showed them date wise (30 days) in horizontal scroll. I have set current date data as active element. But when the date is far like 22nd position and the view is only bound for 5 objects, how can I show the active object data (22nd object) in the center of my screen through smooth scroll on page load? (picture reference attached)
Here is my current code:
import React, { useRef, useEffect } from "react";
const DashboardData = ({
timetable,
sahriToday,
iftarToday,
currentN,
currentD,
setCurrentD,
}) => {
const handleClick = (id) => {
setCurrentD(id);
};
const dateFunc = (dhur, id) => {
let eDate = new Date(dhur);
if (currentN.getDate() === eDate.getDate()) {
setCurrentD(id);
}
}
const myRef = useRef(currentD);
useEffect(() => {
myRef.current?.scrollIntoView ({
behavior: "smooth",
block: "end"
});
}, [currentD])
console.log(currentD);
return (
<>
<div className="mother">
{timetable.map((timetable) => (
<>
<div
className={ currentD === timetable.id ? "dayboom active" : "dayboom" }
onClick={() => handleClick(timetable.id)}
ref={myRef}
>
<h3 className="weekday">{timetable.weekday}</h3>
<h3 className="monthdate">{timetable.day}</h3>
{dateFunc(timetable.edate, timetable.id)}
</div>
</>
))}
</div>
<div className="timeToday">
<div className="sahriToday">
<div>
<h2>
Sahri <span>Time</span>
</h2>
<h3>{sahriToday[0].sahri}</h3>
</div>
</div>
<div className="iftarToday">
<div>
<h2>
Iftar <span>Time</span>
</h2>
<h3>{iftarToday[0].iftar}</h3>
</div>
</div>
</div>
</>
);
};
export default DashboardData;
I have tried scrollIntoView() but that works on the full map data, not the specific one that is active.
If you don't need to save the reference for each of the elements in the map you can try adding a ref only for the element you want the function scrollIntoView do its thing. Something like:
ref={currentD === timetable.id - 2 ? myRef : null}

How can I set an element id to active when another component is scrolled into view?

So I have a sidebar and inside of it multiple buttons. When I click on the particular button, it scrolls into view a component with a certain name(I have one page with multiple components). And it works fine, components scroll into view,
but I want to set a list item id to active, according to the current component in view, so it changes color, but in the other li items active class is removed.
SideBar.jsx:
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<img src={spike} />
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className="row"
id={val.link == val.title ? "active" : ""}
key={key}
onClick={() => {
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
</div>
);
};
So as you can see, I have a ul with list items, and when I click on each one, it scrolls a certain div into view. I also SidebarData.js file, where I store all data as an array:
SidebarData.js
export const SlidebarData = [
{
title: "Home",
link: "home"
},
{
title: "About",
link: "about"
},
{
title: "Services",
link: "services"
},
{
title: "Contact",
link: "contact"
}
];
So when a particular div is in view, I want to set a li id to active, but I can't figure out how I can tell li to do it.
you're changing id instead of class on li and id can’t be duplicate, it is not assigned correctly.
Instead of using id in your code, you might use ref.
here is a sample code to add active class to li based on elements in view.
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [selectedLink, setSelectedLink] = useState("");
return (
<div className="sidebar">
<span className="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<img src={spike} />
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className={`row ${selectedLink === val.link ? "active" : ""}`}
id={val.link}
key={key}
onClick={() => {
setSelectedLink(val.link);
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
</div>
);
};
There are two problems with your solution. One is that your SlidebarData has different titles and links. Thus, when using val.link === val.title in Sidebar hook, you're getting false on the condition, returning the id as blank.
On the other hand, I'm not sure how you're not getting an error with document.getElementById(val.link).scrollIntoView();. The value of val.link is going to be either Home, About, ...; however, you're setting the id of each li as either "active" or blank (""). So, document.getElementById(val.link) should return null and not the element.
EDIT: Does this solve your problem?
If you want to set the id which is active when pressed, do not use the keyword active as you'll have to change all the other id's. Create a state variable currentId (for example), and use it to set the current item you've selected.
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [currentId, setCurrentId] = useState("");
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className="row"
id={val.link}
key={key}
onClick={() => {
setCurrentId(val.link);
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
<div>{currentId}</div>
</div>
);
}
Edit #2: Here is the codesandbox link, so you can have a look at the behaviour of the aforestated code.
https://codesandbox.io/s/fluent-ui-example-forked-rbd9p?file=/index.js
You'll need to use the Intersection Observer API, which will allow you to monitor and react to events which occur when tracked elements intersect a parent element (or the viewport).
Implementing this in React is non-trivial and will likely involve forwarding refs for each tracked element, however, you might find one or more community modules which already exist (e.g. react-intersection-observer).

onClick triggering all sub menus instead of only the clicked one

When I click on an item it should expend some sub items. This is working but if I have two, three or four etc. list items then when I click on one it expands ALL of the sub items for all the list items which is obviously not what I want. How can I fix this code to make it only open expand the one I actually clicked on?
const [sideActive, setSideActive] = useState(false);
const toggleSideActive = () => {
setSideActive(!sideActive);
};
html:
<li>
<div
onClick={toggleSideActive}
className={
sideActive
? `${styles.navList__subheading} row ${styles.row__align_v_center} ${styles.navList__subheading__open}`
: `${styles.navList__subheading} row ${styles.row__align_v_center}`
}
>
<span className={styles.navList__subheading_icon}>
<FaBriefcaseMedical />
</span>
<span className={styles.navList__subheading_title}>
insurance
</span>
</div>
<ul
className={
sideActive
? `${styles.subList}`
: `${styles.subList} ${styles.subList__hidden}`
}
>
<li className={styles.subList__item}>medical</li>
<li className={styles.subList__item}>medical</li>
<li className={styles.subList__item}>medical</li>
</ul>
</li>
You can create a local state for tracking the selected id and show the content based on the state. Also, update the selected Id on click of the tab like below.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState("");
const data = [
{
id: 1001,
name: "Tab - 1",
content: ["test1", "test2"]
},
{
id: 1002,
name: "Tab - 2",
content: ["test21", "test22"]
}
];
return (
<div className="App">
<ul class="parent">
{data.map((v) => (
<li onClick={() => setSelected(selected !== v.id ? v.id : "")}>
{v.name}
{selected === v.id && (
<ul class="content">
{v.content.map((val) => (
<li>{val}</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
);
}
For the below example, click on the tab to see the content.
Working code - https://codesandbox.io/s/long-leaf-cl4cy?file=/src/App.js:0-759
Let me know if you are facing any issues.

React dropdown function triggers all dropdowns instead of selected menu

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

Toggles a class on an element when another element is clicked React

I have this component in react. I would like whenever I click an a tag with class 'btn' to add/toggle a class 'open' to div with class 'smenu' within the same li element. I implemented it naively like the following, but I am sure there should be another more efficient way. Any tips would be appreciated. Thanks in advance
import React, { useState } from "react";
const AccordioMenu = () => {
const [activeP, setActiveP] = useState(false);
const [activeM, setActiveM] = useState(false);
const toggleActiveP = () => {
setActiveP(!activeP);
};
const toggleActiveM = () => {
setActiveM(!activeM);
};
let btnclassesP = ['smenu']
if(activeP){
btnclassesP.push('open')
}
let btnclassesM = ['smenu']
if(activeM){
btnclassesM.push('open')
}
return (
<div className="middle">
<div className="menu">
<li className="item" id="profile">
<a className='btn' href="#" onClick={toggleActiveP}>
Profile
</a>
<div className={btnclassesP.join(' ')}>
Posts
Pictures
</div>
</li>
<li className="item" id="messages">
<a className="btn" href="#" onClick={toggleActiveM}>
Messages
</a>
<div className={btnclassesM.join(' ')}>
New
Sent
</div>
</li>
<li className="item" id="logout">
<a className="btn" href="#">
Logout
</a>
</li>
</div>
</div>
);
};
export default AccordioMenu;
If you want to simplify this even further you could just use one-state value, that way the component has a single source of truth to share.
Let's have a state that stores an array of identifiers. Each identifier is associated with a different set of links. We'll use "message" and "profile" as the identifiers. Naturally, if there is nothing in the array, then all sub-links should be collapsed.
Then we can use just an event-handler to add/remove the identifier into the state array. Lastly, we can use an inline-style to determine whether that set of links corresponding to the identifier should include the open class.
import React, { useState } from "react";
const AccordioMenu = () => {
const [ selectedItems, setSelectedItems ] = useState([])
//event-handler accepts an identifer-string as an argument
const handleSelect = (identifier) => {
//creates a copy of the original state to avoid state-mutation
const selectedItemsCopy = [...selectedItems]
//check if the idenifier that was passed already exists in the state
if(selectedItemsCopy.includes(identifier)){
//it already exists, which means the menu-links are expanded
const foundIndex = selectedItemsCopy.indexOf(identifier)
//you've clicked it to hide it. so remove the identifier from the state
selectedItemsCopy.splice(foundIndex, 1)
setSelectedItems(selectedItemsCopy)
} else {
//if identifier was not found in state. then add it.
setSelectedItems([...selectedItems, identifier])
}
}
return (
<div className="middle">
<div className="menu">
<li className="item" id="profile">
//set up handler to pass identifier
<a className='btn' href="#" onClick={() => handleSelect("profile")}>
Profile
</a>
<div className={selectedItems.includes("profile") ? "smenu open" : "smenu"}>
Posts
Pictures
</div>
</li>
<li className="item" id="messages">
//set up handler to pass identifier
<a className="btn" href="#" onClick={() => handleSelect("message")}>
Messages
</a>
<div className={selectedItems.includes("messages") ? "smenu open" : "smenu"}>
New
Sent
</div>
</li>
<li className="item" id="logout">
<a className="btn" href="#">
Logout
</a>
</li>
</div>
</div>
);
};
export default AccordioMenu;
Christopher Ngo's answer is a good answer that can work.
I just want to provide a different way to handle the same scenario using useReducer.
If you have multiple states that works in conjunction with each other, it sometimes makes it easy to use a reducer, to "co-locate" the related state changes.
import React, { useReducer } from "react";
const initialState = {
profileClass: 'smenu',
menuClass: 'smenu',
};
// 👇 You can see which state are related and how they should change together.
// And you can also see from the action type what each state is doing.
const menuClassReducer = (state, action) => {
switch (action.type) {
case "mark profile as selected":
return { profileClass: 'smenu open', menuClass: 'smenu' };
case "mark menu as selected":
return { profileClass: 'smenu', menuClass: 'smenu open' };
default:
return state;
}
};
const AccordioMenu = () => {
const [{profileClass, menuClass}, dispatch] = useReducer(menuClassReducer, initialState);
const toggleActiveP = () => {
dispatch({type: 'mark profile as selected'})
};
const toggleActiveM = () => {
dispatch({type: 'mark menu as selected'})
};
return (
<div className="middle">
<div className="menu">
<li className="item" id="profile">
<a className="btn" href="#" onClick={toggleActiveP}>
Profile
</a>
1️⃣ 👇
<div className={profileClass}>
Posts
Pictures
</div>
</li>
<li className="item" id="messages">
<a className="btn" href="#" onClick={toggleActiveM}>
Messages
</a>
2️⃣ 👇
<div className={menuClass}>
New
Sent
</div>
</li>
<li className="item" id="logout">
<a className="btn" href="#">
Logout
</a>
</li>
</div>
</div>
);
};
You can see 1️⃣ & 2️⃣ above that you can simply set the state classes that are returned from the reducer.
Those two classes (menuClass & profileClass) are updated automatically on click as events are dispatched from toggleActiveM & toggleActiveP respectively.
If you plan to do something with the "selected" state, you can simply update the reducer by handling new states and you'd still know how each state are updated together in one place.
import React, { useReducer } from "react";
const initialState = {
isProfileSelected: false,
isMenuSelected: false,
profileClass: "smenu",
menuClass: "smenu"
};
const menuClassReducer = (state, action) => {
switch (action.type) {
case "mark profile as selected":
return {
isProfileSelected: true,
isMenuSelected: false,
profileClass: "smenu open",
menuClass: "smenu"
};
case "mark menu as selected":
return {
isProfileSelected: false,
isMenuSelected: true,
profileClass: "smenu",
menuClass: "smenu open"
};
default:
return state;
}
};
const AccordioMenu = () => {
const [
{ profileClass, menuClass, isProfileSelected, isMenuSelected },
dispatch
] = useReducer(menuClassReducer, initialState);
const toggleActiveP = () => {
dispatch({ type: "mark profile as selected" });
};
const toggleActiveM = () => {
dispatch({ type: "mark menu as selected" });
};
return // do something with newly added states;
};

Resources