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"
}
]
Related
I have to fetch data from an API and display it as a list, and then when you click on an item in the list it should take you to another page with details about that item. I don't know how to make the li element take me to another page with details
This is my app.js
import React, { useState, useEffect } from "react";
import CharacterList from "./Components/CharacterList";
import Navbar from "./Components/Navbar";
function App() {
const [characters, setCharacters] = useState([]);
const callBreakingBadAPI = async () => {
const url = `https://www.breakingbadapi.com/api/characters?name`;
const resp = await fetch(url);
const data = await resp.json();
setCharacters(data);
};
useEffect(() => {
callBreakingBadAPI();
}, []);
return (
<div className="container">
<Navbar />
{<CharacterList characters={characters} />}
</div>
);
}
export default App;
this is characterList.js
import React from "react";
import CharacterListItem from "./CharacterListItem";
const CharacterList = ({ characters }) => {
return (
<section>
{characters.map((character) => (
<CharacterListItem character={character} key={character.char_id} />
))}
</section>
);
};
export default CharacterList;
And this is my CharacterListItem.JS
import React from "react";
const CharacterListItem = ({ character }) => {
return (
<ul>
<li>Name: {character.name}</li>
</ul>
);
};
export default CharacterListItem;
I have a characterinfo folder inside my components folder with characterInfo.js where I want to switch to show details about the clicked item.
You can add a onClick handler to your li
const history = useHistory()
<li onClick={() => history.push(/* your url */)}>
Name: {character.name}
</li>
It is recommended not to add onClick listeners to non-interactable html elements for accessibility. If you still want to add the listener, you will have to look at how to make the element accessible. For example, adding tabindex="1" for keyboard users, etc.
Best element type for this usecase would be link (or in some cases, button)
<li>
<Link to={/* your url */}>
Name: {character.name}
</Link>
</li>
You can send the character details from CharacterListItem.js through Link state.
import { Link } from "react-router-dom";
const CharacterList = ({ character }) => {
return (
<Link to="/character-info" state={{data: character}} >Name: {character.name}</Link>
);
};
and from charactorInfo.js, data can be recieved using useLocation hook.
import { useLocation } from "react-router-dom";
const CharacterInfo = () => {
const location = useLocation();
const character = location.state?.data;
console.log(character);
return (
<div>
{/* Display character here */}
</div>)
}
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>
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 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>
)
}
Problem statement:
On clicking the react scroll link, the link is not highlighted(I've used spy) and it is not scrolling to the div instead just landing to the page.
Is there any other efficient way to do it? As i'm learning react by doing
Page Context component:
export const PageContext = createContext({
pageId: 'homepage',
scrollToPage: () => {}
})
There is a homepage component
const Home = () => {
const [pageId, setPageId] = useState('homepage');
const anotherCompRef = React.useRef();
const profileCompRef = React.useRef();
const scrollToPage = (event) => {
let pageId = event.target ? event.target.getAttribute('data-pageId') : null
if (pageId) {
setPageId(pageId);
Scroll.scroller.scrollTo(pageId, {
duration: 500,
delay: 100,
smooth: true,
spy: true,
exact: true,
offset: -80,
})
}
}
const renderer = () => {
switch (pageId) {
case 'profile':
return <ProfileView profileCompRef={profileCompRef} />
default:
return <AnotherView anotherCompRef={anotherCompRef}/>
}
}
return (
<>
<PageContext.Provider value={{pageId, scrollToPage: e => scrollToPage(e)}}>
<Layout>
{renderer()}
</Layout>
</PageContext.Provider>
</>
)
}
Layout component:
const Layout = ( {children} ) => {
return (
<>
<Header/>
<MainContainer children={children}/>
<Footer />
</>
)
}
export default Layout
Profileview component:
const ProfileView = (props) => {
return (
<>
<ProfileContainer id='profile' ref={props.profileCompRef} >
do stuff
</ProfileContainer>
</>
)
}
export default ProfileView
AnotherView component
const AnotherView = (props) => {
return (
<>
<AnotherViewContainer id='anotherView' ref={props.anotherCompRef} >
do stuff
</AnotherViewContainer>
</>
)
}
export default AnotherView
Header component:
const Header = () => {
const pageContext = useContext(PageContext)
return (
<>
<NavbarContainer>
<NavbarMenu>
<NavItem>
<NavLink to='profile' data-pageId='profile' smooth={true} duration={500} spy={true} exact='true' offset={-80} onClick={(e) => pageContext.scrollToPage(e)}>
Profile
</NavLink>
</NavItem>
<NavItem>
<NavLink to='anotherView' data-pageId='anotherView' smooth={true} duration={500} spy={true} exact='true' offset={-80} onClick={(e) => pageContext.scrollToPage(e)}>
Another View
</NavLink>
</NavItem>
</NavbarMenu>
</NavbarContainer>
</>
)
}
export default Header
I have mainly fixed the below mentioned issues.
Enable browser scroll by setting the page height more than the viewport height. Only then scrolling can happen.
Not suitable to add a click event to react-scroll Link. So I developed a custom link.
Also did some modifications in updating the pageId also.
Note - Below mentioned only about updated files.
Header.js
import { useContext } from "react";
import PageContext from "./PageContext";
const Header = () => {
const { pageId, setPageId } = useContext(PageContext);
const scrollTo = (e) => {
e.preventDefault();
setPageId(e.target.dataset.pageid);
};
return (
<>
<div>
<div>
<div>
<a
href="#profile"
data-pageid="profile"
onClick={scrollTo}
className={`${pageId === "profile" ? "active" : ""}`}
>
Profile
</a>
</div>
<div>
<a
href="#anotherview"
data-pageid="anotherview"
onClick={scrollTo}
className={`${pageId === "anotherview" ? "active" : ""}`}
>
Another View
</a>
</div>
</div>
</div>
</>
);
};
export default Header;
Home.js
import { useEffect, useRef, useState } from "react";
import { scroller } from "react-scroll";
import AnotherView from "./AnotherView";
import Layout from "./Layout";
import PageContext from "./PageContext";
import ProfileView from "./ProfileView";
const Home = () => {
const [pageId, setPageId] = useState("homepage");
const anotherCompRef = useRef();
const profileCompRef = useRef();
useEffect(() => {
if (pageId !== "homepage")
scroller.scrollTo(pageId, {
duration: 500,
delay: 100,
smooth: true
});
}, [pageId]);
const renderer = () => {
switch (pageId) {
case "profile":
return <ProfileView profileCompRef={profileCompRef} />;
default:
return <AnotherView anotherCompRef={anotherCompRef} />;
}
};
return (
<>
<PageContext.Provider value={{ pageId, setPageId }}>
<Layout>{renderer()}</Layout>
</PageContext.Provider>
</>
);
};
export default Home;
Layout.js
import { useContext } from "react";
import Header from "./Header";
import PageContext from "./PageContext";
const Layout = ({ children }) => {
const { pageId } = useContext(PageContext);
return (
<>
<Header />
<div id={pageId} children={children} />
</>
);
};
export default Layout;
styles.css
#root {
height: calc(100vh + 100px);
}
a.active {
color: red;
}
https://codesandbox.io/s/scrolling-with-react-scroll-68043895-bqt10?file=/src/Home.jsx
Let me know if you need further support.