I have some menu items in a component and some styling added to the selected menu item. When I refresh the page, I want the selected menu to persist and not the initial state.
import ClaimData from "./Data";
const Services = () => {
const [tabIndex, setTabIndex] = useState(1);
const [selected, setSelected] = useState(1);
return (
<section >
<h3>
Menu
</h3>
<div>
{ClaimData.map((item, index) => {
return (
<div
key={index}
style={
selected === item.tabNum
? {
borderBottom: "3px solid green",
backgroundColor: "#E8E8E8",
}
: null
}
onClick={() => setSelected(item.tabNum)}
>
<p
onClick={() => setTabIndex(item.tabNum)}
style={{ color: item.color }}
>
<item.icon />
<span>{item.title}</span>
</p>
</div>
);
})}
</div>
<div>
{tabIndex === 1 && <MenuOneComponent />}
{tabIndex === 2 && <MenuTwoComponent />}
{tabIndex === 3 && <MenuThreeComponent />}
</div>
</section>
);
};
export default Services;
I have removed some codes for brevity. I would appreciate any help
To presist state on refresh you need to store the state outside of react.
Easiest would propbably be to use localStorage or sessionStorage. But it is of course possible to save it in a database/redis.
One way is to use url to determine which menu to highlight.
Related
how to add delete icon on the end of the label in TreeItem of material UI. Providing delete functionality when clicking only delete icon. Below is categories component to list all nested categories in TreeView of material UI.
const Categories = () => {
const dispatch = useDispatch();
const categories = useSelector((state) => state.category.nestedCategory);
const ids = useRef([]); // all ids for expanded nodes
const [deleteNode, setDeleteNode] = useState(null); //delete category
useEffect(() => {
if (categories) {
// Getting all ids from nested categories
JSON.stringify(categories, (key, value) => {
if (key === "_id") ids.current.push(value);
return value;
});
} else {
// if not getting all category in state then calling api to get all nested categories
getCategoryList(dispatch);
}
}, [dispatch, categories]);
const handleNodeSelect = (event, nodeId) => {
setDeleteNode(nodeId);
console.log(nodeId);
};
const renderTree = (nodes) => (
<TreeItem key={nodes._id} nodeId={nodes._id} label={nodes.name}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</TreeItem>
);
return (
<div className="list">
<Sidebar />
<div className="listContainer">
<Navbar />
<div className="categoryList">
<div className="categoryListTitle">
Add New Category
<Link to="/category/new" className="link">
Add New
</Link>
</div>
<div className="mainContent">
{categories && (
<TreeView
aria-label="rich object"
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
expanded={ids.current}
onNodeSelect={handleNodeSelect}
sx={{
height: 800,
flexGrow: 1,
maxWidth: 400,
overflowY: "hidden",
overflowX: "hidden",
}}
>
{renderTree(categories[0])}
</TreeView>
)}
<div className="deleteNodeForm">
<form className="deleteForm">
<label>Category Name</label>
<input type="text" name="title" placeholder={deleteNode} />
<button className="btnDeleteNode">Delete</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
};
export default Categories;
Is there any way to add icon on the end of label of each TreeItem for delete functionality. I couldn't find proper documentation for custom styling of TreeView.
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>
)
Currently, all accordion panels are being toggled simultaneously. I've tried passing in the index to the click handler, but no luck. How do I compare the current index with the current setActive variable to open and close accordion panels individually? The data I'm working with in production do not have unique ids which is why I'm using index. Thanks for any suggestions!
demo: https://codesandbox.io/s/react-accordion-using-react-hooks-forked-fup4w?file=/components/Accordion.js:141-1323
const Accordion = (props) => {
const [setActive, setActiveState] = useState(0);
const [setHeight, setHeightState] = useState("0px");
const [setRotate, setRotateState] = useState("accordion__icon");
const content = useRef(null);
const toggleAccordion = (index) => {
setActiveState(setActive === index ? "active" : "");
setHeightState(
setActive === "active" ? "0px" : `${content.current.scrollHeight}px`
);
setRotateState(
setActive === "active" ? "accordion__icon" : "accordion__icon rotate"
);
}
return (
<div>
{data.map((item, index) => (
<div key={index} className="accordion__section">
<button
className={`accordion ${setActive}`}
onClick={() => toggleAccordion(index)}
>
<p className="accordion__title">{item.title}</p>
<Chevron className={`${setRotate}`} width={10} fill={"#777"} />
</button>
<div
ref={content}
style={{ maxHeight: `${setHeight}` }}
className="accordion__content"
>
<div>{item.content}</div>
</div>
</div>
))}
</div>
);
};
The problem in your code is that you are generating a general setActive state that is then passed to all your item in your map function. You have to change your state management in order to be able to find for each of the item if they are active or not.
I'd rework a bit your component:
const Accordion = (props) => {
const [activeIndex, setActiveIndex] = useState(0);
const content = useRef(null);
return (
<div>
{data.map((item, index) => {
const isActive = index === activeIndex
return (
<div key={index} className="accordion__section">
<button
className={`accordion ${isActive ? "active" : ""}`}
onClick={() => setActiveIndex(index)}>
<p className="accordion__title">{item.title}</p>
<Chevron className={`${isActive ? "accordion__icon" : "accordion__icon rotate" }`} width={10} fill={"#777"} />
</button>
<div
ref={content}
style={{ maxHeight: `${isActive ? "0px" : `${content.current.scrollHeight}px`}` }}
className="accordion__content">
<div>{item.content}</div>
</div>
</div>
)})}
</div>
);
};
The idea is that for each item in loop you find which on is active and then assign the classes / styles according to this. Could be even more refactored but I let you clean up now that the idea should be there :)
im working on a feedback app,and im updating an state by filtering it, so i can click on a tag and display the feedbacks filtered by tags, im passing the values through context
const handleFiltering = (category) => {
const filteredData = cardInfo?.filter((item: feedbackTypes) => item.categories === category);
setCardInfo(filteredData);
};
return <DataContext.Provider value={{ cardInfo }}>{children}</DataContext.Provider>;
and these are, the card component (where im rendering the feedbacks)
const NewTable: FC = (): JSX.Element => {
const { cardInfo, setCardInfo } = useContext(DataContext);
return (
<>
<Stack spacing={6} mt={4} ml="38.2vw">
{cardInfo ? (
cardInfo?.map((values: feedbackTypes) => (
<div key={values._id}>
<Link
to={{
pathname: '/editFeedback',
state: {
id: values._id,
title: values.title,
feedback: values.text,
category: values.categories
}
}}
>
<Card
title={values.title}
feedback={values.text}
cardId={values._id}
category={values.categories} />
</Link>
</div>
))
) : (
<EmptyCard />
)}
</Stack>
</>
);
};
and the components where the tags are rendered
const NavBar = () => {
const { cardInfo, handleFiltering, permanentData } = useContext(DataContext);
return (
<>
<Flex direction="row">
<Box borderRadius="10px" className={styles.leftCard}>
<Heading fontSize="xl" color="white" mt={14} ml={6}>
Frontend Mentor
</Heading>
<Text mt={2} ml={6}>
Feedback Board
</Text>
</Box>
<Box borderRadius="10px" className={styles.leftCardSort}>
<Heading fontSize="xl" color="white" mt={10} ml={6}></Heading>
<Text ml={4} className={styles.tagSort}>
{permanentData?.map((values, idx) => (
<div key={idx}>
<Tag
className={selectedTag?.includes(values as never) ? styles.tagSelected : styles.tagUnselected}
onClick={(e) => {
handleFiltering(values);
handleBtnStyle(values as never);
}}
>
{values}
</Tag>
</div>
))}
</Text>
</Box>
whenever i click on each tag, the feedbacks that contain those tags are filtered correctly, but when i click on another tag after selecting one, it does not render the other feedbacks until i click on 'All' (and thats because im fetching all the data again), i dont know how could i re render other feedbacks after i already clicked on one, because the feedback component needs to be filtered just like the tag is filtering them by categories, i dont know what logic to follow from here on
first tag selected
second tag selected after the first
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