I have a functioning "dropdown menu" - clicking on "menu item" routes to a different component. I want to change the style of "dropdown menu item" after clicking, so the next time "dropdown menu" opens, "item" that has been clicked has a different look - indicating active state.
Behavior I want to replicate can be observed on the page I'm re-writing in React : code-learning.uk , after clicking on item from black menu dropdown button (USEFUL ST), "menu item" changes color to blue.
DropdownMenu
import { useState } from "react"
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
import { Link } from "react-router-dom";
const DropdownMenu = () => {
const [click, setClick] = useState("");
const handleClick = () => setClick("hide-menu");
return (
<div className={`${click}`}>
{MenuItemContentSchool.map((item, index) => {
return (
<Link to={item.link} onClick={handleClick} key={item.title}>
{item.title}
</Link>
)
} )}
</div>
)
}
export default DropdownMenu
NaviMain.js
import { useState } from "react"
import DropdownMenu from "./DropdownMenu";
const NaviMain = () => {
const [disp, setDisp] = useState(false);
const hoverOn = () => setDisp(true)
const hoverOff = () => setDisp(false)
return (
<nav>
<ul>
<li onMouseEnter={hoverOn} onMouseLeave={hoverOff}>
<a>school</a>
{ disp && <DropdownMenu /> }
</li>
</ul>
</nav>
)
}
export default NaviMain
App.js
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import NaviMain from "./components/NaviMain";
import VerticalAlign from "./pages/VerticalAlign";
import Flexbox from "./pages/Flexbox";
function App() {
return (
<Router>
<div className="App">
<NaviMain />
<Routes>
<Route path="/verticalalign" element={<VerticalAlign />} />
<Route path="/flexbox" element={<Flexbox/>} />
</Routes>
</div>
</Router>
);
}
export default App;
MenuItemContentSchool
export const MenuItemContentSchool = [
{
title:"v align",
link:"/verticalalign",
},
{
title:"flexbox",
link:"/flexbox",
},
]
Do a check to see if the current page matches the items link, then add a class to the link for that item.
something like:
const activeClass = window.location.pathname.startsWith(item.link) ? "isActive" : "";
<Link to={item.link} onClick={handleClick} key={item.title} className={activeClass}>
{item.title}
</Link>
Related
I want to load a component depending in what page are the user.
Pages:
Executables
Shop
In the main screen I have a sidebar with 2 icons that i want the primary button sets the Executables Page and the second shop page.
Like having a web page with no routes and rendering components depending the user selection.
My code:
Components/Dashboard.tsx
import styled from "styled-components"
import Executable from "./Executable"
import Navbar from "./Navbar"
import { useEffect } from "react"
type EntryProps = {
section: string
}
const Dashboard = ({ section }: EntryProps) => {
var TypeElement
useEffect(() => {
if (section === "executables") {
TypeElement = (
<div className="grid">
<div className="row">
<Executable />
</div>
</div>
)
}
}, [section])
return (
<Section>
<Navbar />
{TypeElement}
</Section>
)
}
Components/Sidebar.tsx
import styled from "styled-components"
import { FaBars } from "react-icons/fa"
import { BsFileEarmarkBinary } from "react-icons/bs"
import { BiLogOut } from "react-icons/bi"
import { AiOutlineShoppingCart } from "react-icons/ai"
import { IoMdSettings } from "react-icons/io"
import { useState } from "react"
type SidebarProps = {
section: string
setSection: Function
}
const Sidebar = ({ section, setSection }: SidebarProps) => {
const [disabled, setDisabled] = useState(true)
const handleDisabled = () => setDisabled(!disabled)
return (
<Aside id="aside">
<div
className={disabled ? "brand center" : "brand"}
onClick={handleDisabled}
>
<FaBars />
</div>
<ul className="links">
<li>
<BsFileEarmarkBinary />
<span
className={disabled ? "disabled" : ""}
onClick={(e) => {
console.log("Executables")
setSection("executables")
}}
>
Executables
</span>
</li>
<li>
<AiOutlineShoppingCart />
<span
className={disabled ? "disabled" : ""}
onClick={(e) => {
e.preventDefault()
setSection("shop")
}}
>
Shop
</span>
</li>
<li>
<IoMdSettings />
<span
className={disabled ? "disabled" : ""}
onClick={setSection("settings")}
>
Settings
</span>
</li>
</ul>
<div className="logout">
<BiLogOut />
</div>
</Aside>
)
}
Pages/DashboardPage.tsx
import styled from "styled-components"
// Components
import Sidebar from "../components/Sidebar"
import Rightsidebar from "../components/Rightsidebar"
import Dashboard from "../components/Dashboard"
import { useState } from "react"
const DashboardPage = () => {
const [page, setPage] = useState("executables")
const setSection = (name: string) => {
setPage(name)
}
return (
<Div>
<Sidebar section={page} setSection={setSection} />
<Dashboard section={page} />
<Rightsidebar />
</Div>
)
}
You are changing the value of TypeElement conditionally according to the value of the section. TypeElement is not a state, so after changing the value of the TypeElement component is not rerendered, and the updated value is not showing on the UI. Here conditional rendering might be a good solution.
<Section>
<Navbar />
{section==='executables'? <Executable />: <Shop/>}
</Section>
In my App I have a HeaderLogo component, with <h1> containing animation (inside its head-main class). I would like to re-render this component to trigger animation, after onclick event in <NavLink>.
<NavLink> is inside DropdownMenu, which is inside MainNavi.
HeaderLogo
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
Dropdown Menu
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
import { useState } from "react";
import { NavLink } from "react-router-dom";
const DropdownMenu2 = () => {
const [click, setClick] = useState("");
const handleClick = () => {
setClick("hide-menu");
}
return (
<div className={`dropdown-holder-us ${click}`}>
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className='d-content-us'
onClick={handleClick}
key={item.title}
>
{item.title}
</NavLink>
)
} )}
</div>
)
}
export default DropdownMenu2
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
//...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import DropdownMenu2 from "./DropdownMenu2";
const NaviMain = () => {
return (
<nav>
<ul className="nav-main">
<li className="nav-main__button">
<a>school</a>
<DropdownMenu2 />
</li>
</ul>
</nav>
)
}
export default NaviMain
I do not know whether this will work or not but u can try the following solution:
set an id:
<h1 id="testing" className="head-main">learning curve</h1>
Change
const handleClick = () => {
setClick("hide-menu");
}
to
const handleClick = () => {
setClick("hide-menu");
let element = document.getElementById('#testing').
element.classList.remove("head-main");
element.classList.add("head-main");
}
Please let me know whether this solution works or not.
I'm trying to create "dropdown menu" in React. I've used useState inside functions in onMouseEnter and onMouseLeave events (in dropdown-holder-us div / NaviMain), to toggle display of "dropdown menu". Last thing is to make "dropdown menu" disappear , when clicking on DropdownMenuItem. Could someone hint me how to achieve this?
App.js
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
import VerticalAlign from "./pages/VerticalAlign";
import Flexbox from "./pages/Flexbox";
function App() {
return (
<Router>
<div className="App">
<NaviMain />
<Routes>
<Route path="/" element={<Info />} />
<Route path="/verticalalign" element={<VerticalAlign />} />
<Route path="/flexbox" element={<Flexbox/>} />
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain.js
import { useState } from "react"
import DropdownMenuItem from "./sub-components/DropdownMenuItem"
const NaviMain = () => {
const [disp, setDisp] = useState("hide-menu");
const hoverOn = () => {
setDisp("show-menu")
}
const hoverOff = () => {
setDisp("hide-menu")
}
return (
<nav>
<ul">
<li onMouseEnter={hoverOn} onMouseLeave={hoverOff}>
<a className="hover-pointer">school</a>
<div className={`dropdown-holder-us ${disp}`}>
<DropdownMenuItem title="v align" link={"/verticalAlign"}/>
<DropdownMenuItem title="flexbox" link={"/flexbox"} />
</div>
</li>
</ul>
</nav>
)
}
export default NaviMain
DropdownMenuItem.js
import { Link } from "react-router-dom";
const DropdownMenuItem = ({title , link}) => {
return (
<>
<Link to={link}">
{title}
</Link>
</>
)
}
export default DropdownMenuItem
I think easiest solution is to make disp a boolean which decides whether the dropdown menu is shown or not. I have shortened the dropdown menu to a <DropdownMenu> component for better legibility (you might want to do this in your code to, might be easier to maintain).
const NaviMain = () => {
const [display, setDisplay] = useState(false);
const hoverOn = () => {
setDisplay(true)
}
const hoverOff = () => {
setDisplay(false)
}
return (
<nav>
<ul">
<li onMouseEnter={hoverOn} onMouseLeave={hoverOff}>
<a className="hover-pointer">school</a>
{
display ? <DropdownMenu / >
}
</li>
</ul>
</nav>
)
}
If it is difficult to make dropdown menu appear and disappear like this,
another way to make it work is to attach disp to a css class which decides if DropdownMenu is display: block or display: none.
After rebuilding NaviMain.js (like #cSharp suggested in his answer), Ive created separate components DropdownMenu (containing logic) and MenuItemContentSchool (with content). So the "hover" effect, managed by mouse evenets, is in li - which is a parent for "DropdownMenu". And the desired disappearance of "DropdownMenu" is managed by click event, inside the container div. Quite simple, inspired by Brian Design video.
NaviMain.js
import { useState } from "react"
import DropdownMenu from "./DropdownMenu";
const NaviMain = () => {
const [disp, setDisp] = useState(false);
const hoverOn = () => setDisp(true)
const hoverOff = () => setDisp(false)
return (
<nav>
<ul>
<li className= onMouseEnter={hoverOn} onMouseLeave={hoverOff}>
<a>school</a>
{ disp && <DropdownMenu /> }
</li>
</ul>
</nav>
)
}
export default NaviMain
DropdownMenu
import { useState } from "react"
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
import { Link } from "react-router-dom";
const DropdownMenu = ( {disp} ) => {
const [click, setClick] = useState("")
const handleClick = () => {
setClick("hide-menu")
}
return (
<div className={`dropdown-holder-us ${disp} ${click}`}>
{MenuItemContentSchool.map((item, index) => {
return (
<Link to={item.link} className="d-content-us" onClick={handleClick}>
{item.title}
</Link>
)
} )}
</div>
)
}
export default DropdownMenu
MenuItemContentSchool
export const MenuItemContentSchool = [
{
title:"v align",
link:"/verticalAlign"
},
{
title:"flexbox",
link:"/flexbox"
}
]
I have a button that represents shop now that upon click it redirects you on different pages and the card elements have been mapped on external data.
I want this button to navigate to different pages kindly help.
const data =[
{
id:"Extensions",
Title:"Electrical Collections",
img:"/Assets/Electricals/Extlogo.png",
},
{
id:"Phone Accesorries",
Title:"Phone Accessories",
img:"/Assets/Phone/phoneacc.png",
},
{
id:"Homeware",
Title:"Homeware Collections",
img:"/Assets/Home/home.png",
},
]
function Home() {
let Navigate =useNavigate();
const handleElectricalPage = () =>{
Navigate("/extensionproduct")
}
<div className='cardContainer'>
{data.map((item )=>(
<card>
<div className='imageContainer'>
<img src={item.img} alt='Extension logo'/>
</div>
<div className='contentsContainer'>
<h2>{item.Title}</h2>
<div className='iconButtonContainer'>
<button onClick={handleElectricalPage}>Shop Now</button>
<ArrowForwardIcon className='arrowIcon'/>
</div>
</div>
Example from the react router https://reactrouter.com/docs/en/v6/api#usenavigate
navigate("../success", { replace: true });
You need to make an onClick handler (handleElectricalPage) dynamically, consider something like this.
function Home() {
let navigate =useNavigate();
return (
<div className='cardContainer'>
{data.map((item) => (
<card>
<button
onClick={() => navigate(`/externalProduct/${item.id}`)}
>
Shop Now
</button>
</card>
)}
</div>
}
You can also use Link which handles everything by itself
<Link to={`/externalProduct/${item.id}`}>
<button>Shop now</button>
</Link>
App.jsx
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<div className="App">
<Routes>
<Route path="/" element={<Home />}>
<Route path="post/:id" element={<Detail />} />
</Route>
</Routes>
</div>
);
}
export default App;
Post.jsx
import React, { useState } from "react";
import {useNavigate} from 'react-router-dom'
export default function Post(props) {
const navigate = useNavigate()
{props.posts.map((post) => {
return (
<div className="post__container" key={post.id}>
<h4>
{post.id} {post.title}
</h4>
<p>{post.body}</p>
<button onClick={() => {return navigate(`${post.id}`)}}>Detail</button>
</div>
);
})}
}
Detail.jsx
import axios from 'axios'
import React, {useEffect, useState } from 'react'
import {useParams} from 'react-router'
export default function Detail (props) {
const params = useParams()
const [posts, setPosts] = useState({})
async function getById(id) {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts/" + id)
setPosts(response.data)
}
useEffect(() => {
getById(params.id)
}, [params.id])
return (
<div>
<h2>Detail page: {params.id}</h2>
<h4>{posts.title}</h4>
<h5>{posts.body}</h5>
</div>
)
}
Story
I'm using this react.js template http://skote-v-light.react.themesbrand.com/email-read from https://themeforest.net/item/skote-react-admin-dashboard-template/26318700 for building my react app.
I use and modify the sidebar component from the template and name it Sidebar.js.
There is SidebarContent.js for building the content inside the sidebar.
There is MenuComponent.js inside SidebarContent.js for building the menu inside the sidebar.
MenuComponent.js takes a list from menuList.js and renders it.
There are two dummy pages which are Route/Summary page (Summary.js) and Payload page (Payload.js) inside the Sidebar. I combine them all inside PageWithSidebar.js.
Question
Why don't always the ABC, DEF, etc folders in the sidebar open when I click them?
Ideal Condition I Wanted
Every folder (ABC, DEF, etc) inside the sidebar always opens when I click one of them.
Actual Condition
Please watch this demo to understand the context: https://drive.google.com/file/d/1Lig1SKKRtyRqxWm_OYNkuQennrytV0jo/view?usp=sharing
Steps:
Click the Route/Summary option inside the ABC folder -> Summary page is shown (as I wanted)
Click the Payload option inside the ABC folder -> Payload page is shown (as I wanted)
Click another Route/Summary option inside the ABC folder -> Summary page is shown (as I wanted)
Close the ABC folder -> The folder closes (as I wanted)
Click the DEF folder -> The folder doesn't open (not I wanted)
There is no error or warning log in the browser console.
Code
PageWithSidebar.js:
import React from 'react'
import { Route, Switch } from "react-router-dom";
// COMPONENTS
import Sidebar from '../../components/Menu/Sidebar/Sidebar'
// PAGES
// ROUTE
import Summary from '../Routes/Summary/Summary'
// PAYLOAD
import Payload from '../Payload/Payload'
const PageWithSidebar = () => {
return(
<div id="layout-wrapper">
<Sidebar/>
<div className="main-content">
<Switch>
{/* ROUTE */}
<Route path="/route/summary/location=:locationName">
<Summary/>
</Route>
<Route path="/payload/location=:locationName">
<Payload/>
</Route>
</Switch>
</div>
</div>
)
}
export default PageWithSidebar
Sidebar.js:
import React from "react"
import { Link } from "react-router-dom"
// COMPONENTS
import SidebarContent from './SidebarContent'
// IMAGES
import IconClose from '../../../images/icons/close.svg'
import IconOpen from '../../../images/icons/open.svg'
const Sidebar = () => {
const toogleSidebar = () => {
var body = document.body;
body.classList.toggle("vertical-collpsed");
body.classList.toggle("sidebar-enable");
}
return (
<React.Fragment>
<div className="vertical-menu">
<div className="navbar-brand-box">
<Link to='/' className="logo logo-dark">
{/* SIDEBAR IS CLOSED */}
<span className="logo-sm">
{/* TOOGLE SIDEBAR BUTTON */}
<img src={IconOpen} alt='' className='icon-open' onClick={toogleSidebar}/>
</span>
{/* SIDEBAR IS OPEN */}
<span className="logo-lg sidebar-is-open-container">
{/* TOOGLE SIDEBAR BUTTON */}
<img src={IconClose} alt='' className='icon-close' onClick={toogleSidebar}/>
</span>
</Link>
</div>
{/* SIDEBAR CONTENT */}
<SidebarContent />
<div className="sidebar-background"></div>
</div>
</React.Fragment>
)
}
export default Sidebar
SidebarContent.js:
import PropTypes from "prop-types"
import React, { useEffect, useRef, useState } from "react"
import { withRouter, Link, useLocation } from "react-router-dom"
// Import Scrollbar
import SimpleBar from "simplebar-react"
// MetisMenu
import MetisMenu from "metismenujs"
//i18n
import { withTranslation } from "react-i18next"
// COMPONENTS
import MenuComponent from './MenuComponent'
// IMAGES
import IconFolderGray from '../../../images/icons/folder-gray.svg'
import IconFolderRed from '../../../images/icons/folder-red.svg'
import IconLogout from '../../../images/icons/logout.svg'
// MENU ITEMS
import menuList from './menuList/menuList'
// STYLES
import './styles.scss'
import logoutUser from '../../../utils/authentication/logOut'
const SidebarContent = props => {
// MY OWN CODE SECTION
const location = useLocation()
const pathName = location['pathname']
let locationName = ''
if(pathName.includes('route')) {
locationName = pathName.split('/')[3].replace('location=', '')
}
else {
locationName = pathName.split('/')[2].replace('location=', '')
}
// console.log('SidebarContent, locationName:', locationName)
// TEMPLATE CODE SECTION
const ref = useRef()
useEffect(() => {
const pathName = props.location.pathname
const initMenu = () => {
new MetisMenu("#side-menu")
let matchingMenuItem = null
const ul = document.getElementById("side-menu")
const items = ul.getElementsByTagName("a")
for (let i = 0; i < items.length; ++i) {
if (pathName === items[i].pathname) {
matchingMenuItem = items[i]
break
}
}
if (matchingMenuItem) {
activateParentDropdown(matchingMenuItem)
}
}
initMenu()
}, [props.location.pathname])
useEffect(() => {
ref.current.recalculate()
})
function scrollElement(item) {
if (item) {
const currentPosition = item.offsetTop
if (currentPosition > window.innerHeight) {
ref.current.getScrollElement().scrollTop = currentPosition - 300
}
}
}
function activateParentDropdown(item) {
item.classList.add("active")
const parent = item.parentElement
const parent2El = parent.childNodes[1]
if (parent2El && parent2El.id !== "side-menu") {
parent2El.classList.add("mm-show")
}
if (parent) {
parent.classList.add("mm-active")
const parent2 = parent.parentElement
if (parent2) {
parent2.classList.add("mm-show") // ul tag
const parent3 = parent2.parentElement // li tag
if (parent3) {
parent3.classList.add("mm-active") // li
parent3.childNodes[0].classList.add("mm-active") //a
const parent4 = parent3.parentElement // ul
if (parent4) {
parent4.classList.add("mm-show") // ul
const parent5 = parent4.parentElement
if (parent5) {
parent5.classList.add("mm-show") // li
parent5.childNodes[0].classList.add("mm-active") // a tag
}
}
}
}
scrollElement(item);
return false
}
scrollElement(item);
return false
}
// MY OWN CODE SECTION
const itemList = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO', 'PQR']
const [ selectedItem, setSelectedItem ] = useState(itemList.indexOf(locationName.toUpperCase()))
return (
<React.Fragment>
<SimpleBar style={{ maxHeight: "100%" }} ref={ref}>
<div id="sidebar-menu">
<ul className="metismenu list-unstyled sidebar-content-menu-container" id="side-menu">
{/* TITLE */}
<li className="menu-title sidebar-content-site-profile-title">Dummy Locations</li>
{/* ITEMS */}
{
itemList.map((item, index) => (
<li
key={index}
className={selectedItem === index ? 'sidebar-content-dropdown-selected' : 'sidebar-content-dropdown'}
// onClick={() => setSelectedItem(index)}
>
{/* TITLE */}
<Link to="/" className="has-arrow dropdown-link">
{/* FOLDER ICON */}
<img
src={selectedItem === index ? IconFolderRed : IconFolderGray}
alt=''
className='bx sidebar-content-icon-folder-gray'
/>
{/* TEXT */}
<span className='dropdown-span'>
{item}
</span>
</Link>
{/* MENU COMPONENT*/}
<ul className="sub-menu" aria-expanded="false">
<div className='border-line'></div>
<MenuComponent list={menuList} pathName={item.toLowerCase()}/>
</ul>
</li>
))
}
{/* PROFILE */}
<li className='sidebar-content-single-item-container'>
<Link to='/'>
<div onClick={logoutUser}>
<img src={IconLogout} alt='' className='sidebar-content-icon-single-item'/>
<span className='sidebar-content-span-single-item'>Logout</span>
</div>
</Link>
</li>
</ul>
</div>
</SimpleBar>
</React.Fragment>
)
}
SidebarContent.propTypes = {
location: PropTypes.object,
t: PropTypes.any,
}
export default withRouter(withTranslation()(SidebarContent))
MenuComponent.js:
import React, { useState } from 'react'
import { Link } from "react-router-dom"
const MenuComponent = (props) => {
const [ selectedItem, setSelectedItem ] = useState(0)
return(
props['list'].map((item, index) => (
<li
key={index}
onClick={() => setSelectedItem(index)}
>
<Link
to={`${item['path']}/location=${props['pathName']}`}
className='menu-component-root'
>
{/* CIRCLE */}
<div className={selectedItem === index ? 'circle-selected' : 'circle'}></div>
{/* TEXT */}
<p className='text'>
{item['text']}
</p>
</Link>
</li>
))
)
}
export default MenuComponent
menuList.js:
const menuList = [
{
path: '/route/summary',
text: 'Route/Summary'
},
{
path: '/route/summary',
text: 'Route/Summary'
},
{
path: '/payload',
text: 'Payload'
},
{
path: '/route/summary',
text: 'Route/Summary'
},
{
path: '/route/summary',
text: 'Route/Summary'
},
{
path: '/route/summary',
text: 'Route/Summary'
},
{
path: '/route/summary',
text: 'Route/Summary'
}
]
export default menuList
Summary.js:
import React from "react"
import { Row, Col } from "reactstrap"
// STYLES
import './styles.scss'
const Summary = () => {
return (
<React.Fragment>
<div className="container-fluid route-summary-root">
<Row>
<Col className="col-12">
{/* TOP CONTAINER */}
<div className='top-container'>
{/* LEFT CONTAINER */}
<div className='top-left-container'>
<h3 className='title'> Route/Summary Page</h3>
</div>
</div>
</Col>
</Row>
</div>
</React.Fragment>
)
}
export default Summary
Payload.js:
import React from "react"
import { Row, Col } from "reactstrap"
// STYLES
import './styles.scss'
const Payload = () => {
return (
<React.Fragment>
<div className="container-fluid payload-root">
<Row>
<Col className="col-12">
{/* TOP CONTAINER */}
<div className='top-container'>
{/* LEFT CONTAINER */}
<div className='top-left-container'>
<h3 className='title'>Payload Page</h3>
</div>
</div>
</Col>
</Row>
</div>
</React.Fragment>
)
}
export default Payload
Note: let me know if the question is confusing. I will try to update it.