I am trying to implement a custom dropdown menu in react, but I am facing some weird issues.
I can't understand why the items in the Services menu don't show up onMouseEnter.
With debugger, the function onMouseEnter gets called, but not able to see list of items
Thanks in advance.
import Dropdown from '../Dropdown'
import React, { useState } from "react";
import { Link } from "react-router-dom";
const Navbar = () => {
const [click, setClick] = useState(false)
const [dropdown, setDropdown] = useState(false)
const handleClick = () => setClick(!click)
const closeMobileMenu = () => setClick(false)
const onMouseEnter = () => {
if (window.innerWidth < 960) {
setDropdown(false)
} else {
setDropdown(true)
}
}
const onMouseLeave = () => {
if (window.innerWidth < 960) {
setDropdown(false)
} else {
setDropdown(false)
}
}
return (
<>
<nav className="nb">
LOGO
<div className='menu-icon' onClick={handleClick}>
<i className={click ? 'fas fa-times' : 'fas fa-bars'}/>
</div>
<ul>
<li
className='nav-item'
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<Link
to='/services'
className='nav-links'
onClick={closeMobileMenu}
>
Services <span className='fas fa-caret-down'></span>
</Link>
{dropdown && <Dropdown />}//here is the problem. I can't see items onMouseEnter
</li>
</ul>
</nav>
</>
)
}
export default Navbar
Here is the Dropdown.js class
import "./Dropdown.css";
import React, { useState } from "react";
import { Link } from "react-router-dom";
const MenuItems = [
{
title: 'Marketing',
path: '/marketing',
cName: 'dropdown-links'
},
]
const Dropdown = () => {
const [click, setClick] = useState(false);
const handleClick = () => setClick(!click);
return (
<>
<ul
onClick={handleClick}
className={click ? "dropdown-menu clicked" : "dropdown-menu"}
>
{MenuItems && MenuItems.map((item, index) => {
return (
<li key={index}>
<Link
className={item.cName}
to={item.path}
onClick={() => setClick(false)}
>
{item.title}
</Link>
</li>
);
})}
</ul>
</>
);
};
export default Dropdown;
you can use these links in index.html so that we have the same icon
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.15.3/css/all.css"
integrity="sha384-SZXxX4whJ79/gErwcOYf+zWLeJdY/qpuqC4cAa9rOGUstPomtqpuNWT9wdPEn2fk"
crossorigin="anonymous"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght#700&display=swap"
rel="stylesheet"
/>
Related
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 am experimenting the GSAP tutorial to understand how it works with React. I can't figure out why I cannot click on my navigation link anymore when the following code has been implemented in my home page :
My Circle Component
const Circle = forwardRef(({ size, delay }, ref) => {
const el = useRef();
useImperativeHandle(
ref,
() => {
return {
moveTo(x, y) {
gsap.to(el.current, { x, y, delay });
},
};
},
[delay]
);
return <div className={`circle ${size}`} ref={el}></div>;
});
(extract of) My Homepage
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import Circle from "../../components/circle/Circle";
const Home = () => {
//special cursor
const circleRefs = useRef([]);
useEffect(() => {
circleRefs.current.forEach((ref) =>
ref.moveTo(window.innerWidth / 2, window.innerHeight / 2)
);
const onMove = ({ clientX, clientY }) => {
circleRefs.current.forEach((ref) => ref.moveTo(clientX, clientY));
};
window.addEventListener("pointermove", onMove);
return () => window.removeEventListener("pointermove", onMove);
}, []);
const addCircleRef = (ref) => {
if (ref) circleRefs.current.push(ref);
};
return (
<div className="home">
<div className="topbar">
<nav>
<ul>
<Link to={`/characters`}>
<li>Characters</li>
</Link>
<Link to={`/comics`}>
<li>Comics</li>
</Link>
<Link to={`/favorites`}>
<li>Favorites</li>
</Link>
</ul>
</nav>
</div>
<Circle size="sm" ref={addCircleRef} delay={0} />
<Circle size="md" ref={addCircleRef} delay={0.1} />
<Circle size="lg" ref={addCircleRef} delay={0.2} />
</div>
);
};
export default Home;
Is it because ref is placed on the cursor ?
Link to favorites page not working in react
Here is the code for my navbar
The fav button is found under menu icons the first one.
I have it in a link and then the nav item icon inside this link
import React, { useState, useEffect, useRef, useContext } from "react";
import "./Navbar.css";
import "../App.css";
import { CSSTransition } from "react-transition-group";
import { ReactComponent as HeartIcon } from "../icons/heart.svg";
import { ReactComponent as CartIcon } from "../icons/cart.svg";
import { ReactComponent as AccountIcon } from "../icons/account.svg";
import { ReactComponent as RightArrowIcon } from "../icons/rightArrow.svg";
import { ReactComponent as LeftArrowIcon } from "../icons/leftArrow.svg";
import { ReactComponent as OrdersIcon } from "../icons/orders.svg";
import { ReactComponent as SettingIcon } from "../icons/setting.svg";
import { ReactComponent as DarkModeIcon } from "../icons/darkMode.svg";
import { ReactComponent as LocationIcon } from "../icons/location.svg";
import { ReactComponent as CurrencyIcon } from "../icons/currency.svg";
import logo from "../logo/tryb_logo_medium.png";
import { Link, Route, Router } from "react-router-dom";
import { Button, Div, Icon, SideDrawer, Text } from "atomize";
import { Dropdown } from "react-bootstrap";
//Cart
import { Container, Anchor } from 'atomize'
import { ShopContext } from '../context/shopContext'
import "../context/AuthContext";
import fire from "../config/fire";
import Login from "./Login";
import Cart from './../components/Cart';
import { createGlobalStyle } from "styled-components";
import { Label,Switch } from "atomize";
function Nav() {
//Use fucntion sopen cart from shopConext
const { openCart, checkout } = useContext(ShopContext)
return (
<Navbar>
<Cart />
{/* LOGO */}
<Link to="/">
<img className="_navbar-logo" src={logo} />
</Link>
{/* Hamburger Menu */}
<div onClick="" className="menu-btn">
<div className="menu-btn__burger"></div>
</div>
<div className="menu-items">
<Link to="/new-in">New In</Link>
<Link to="/prints">Prints</Link>
<Link to="/bestsellers">Bestsellers</Link>
<Link to="/inspiration">Inspiration</Link>
</div>
{/* Menu Icons */}
<Link to="/favorites">
<NavItem class="hide-icons" icon={<HeartIcon class="icon" />} />
</Link>
{/* Changes for shopping cart count */}
<li className="_nav-item" onClick={() => openCart()}>
<a href="#" className="icon-button">
<CartIcon class="icon" id="cart" />
<span className="cart-counter">{checkout.lineItems?.length || 0}</span>
</a>
</li>
<NavItem icon={<AccountIcon class="icon" />}>
<DropdownMenu>Test</DropdownMenu>
</NavItem>
</Navbar >
);
}
function Navbar(props) {
return (
<nav className="_navbar">
<ul className="_navbar-buttons">{props.children}</ul>
</nav>
);
}
function NavItem(props) {
const [open, setOpen] = useState(false);
return (
<li className="_nav-item">
<div className="icon-button" onClick={() => setOpen(!open)}>
{props.icon}
</div>
{open && props.children}
</li>
);
}
//dropdown menu for navbar
function DropdownMenu() {
const [activeMenu, setActiveMenu] = useState("main");
const [menuHeight, setMenuHeight] = useState(null);
const dropdownRef = useRef(null);
const [open, setOpen] = useState(false);
useEffect(() => {
setMenuHeight(dropdownRef.current?.firstChild.offsetHeight);
}, []);
function calcHeight(el) {
const height = el.offsetHeight;
setMenuHeight(height);
}
function DropdownItem(props) {
return (
<div
className="menu-item"
onClick={() => props.goToMenu && setActiveMenu(props.goToMenu)}
>
<span className="icon-button">{props.leftIcon}</span>
{props.children}
<span className="icon-right">{props.rightIcon}</span>
</div>
);
}
const [user, setUser] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [hasAccount, setHasAccount] = useState("");
//Logout
const handleLogout = () => {
fire.auth().signOut();
};
const authListener = () => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user);
} else {
setUser("");
}
});
};
useEffect(() => {
authListener();
}, []);
return (
<>
<div className="dropdown" style={{ height: menuHeight }} ref={dropdownRef} >
<CSSTransition
in={activeMenu === "main"}
timeout={500}
classNames="menu-primary"
unmountOnExit
onEnter={calcHeight}
>
<div className="menu">
<DropdownItem
leftIcon={<AccountIcon class="icon" />}
rightIcon={<RightArrowIcon class="icon" />}
goToMenu="my-account"
>
<a > My Account</a>
</DropdownItem>
<DropdownItem leftIcon={<OrdersIcon class="icon" />}>
My Orders
</DropdownItem>
<DropdownItem
leftIcon={<SettingIcon class="icon" />}
goToMenu="settings"
className="dropdown"
>
Settings
</DropdownItem>
</div>
</CSSTransition>
{/* Settings Menu */}
<CSSTransition
in={activeMenu === "settings"}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}
>
<div className="menu">
<DropdownItem
goToMenu="main"
leftIcon={<LeftArrowIcon class="icon" />}
>
<h2>Settings</h2>
</DropdownItem>
<DropdownItem leftIcon={<LocationIcon class="icon" />}>
Location
</DropdownItem>
<DropdownItem leftIcon={<CurrencyIcon class="icon" />}>
Currency
</DropdownItem>
</div>
</CSSTransition>
{/* My Account Menu */}
<CSSTransition
in={activeMenu === "my-account"}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}
>
<div>
<DropdownItem
goToMenu="main"
leftIcon={<LeftArrowIcon class="icon" />}
>
<h2>My Account</h2>
</DropdownItem>
{/*
<Link to="/account">
<DropdownItem leftIcon="">
Account
</DropdownItem></Link> */}
{user ? (
<>
<span>
<Link to="/account">
<DropdownItem leftIcon="">Account</DropdownItem>
</Link>
<Link to="/" onClick={handleLogout} >
<DropdownItem>Logout</DropdownItem>
</Link>
</span>
</>
) : (
<>
<span>
<Link to="/login" onClick={() => setOpen(!open)}>
<DropdownItem>Login</DropdownItem>
</Link>
</span>
</>
)}
</div>
</CSSTransition>
</div></>
);
}
export { Nav };
Here is my favorites page
import React, { useContext, useState } from "react";
import { Text, Button, Row, Col, Container, Image, ThemeProvider } from "atomize";
import { ShopContext } from "../../context/shopContext";
const theme = {
grid: {
colCount: 8,
gutterWidth: 0
}
};
const FavoritesPage = () => {
const [favorites, setFavorites] = useState(JSON.parse(localStorage.getItem("favorites") || "[]"));
const { addItemToCheckout } = useContext(
ShopContext
);
const favoritesList = favorites.map(product => (
<Container>
<ThemeProvider theme={theme}>
<Row bg="black" rounded="lg" d="flex"
m="6rem">
<Col size={{ xs: 8, md:5, lg: 5 }}>
<Image src={product.images[0].src} w="20rem" p="2rem"/>
</Col>
<Col align="space-around" >
<Text tag="h1" textColor="white" textWeight="200" m={{ y: "2rem" }} >
{product.title}
</Text>
<Text tag="h3" textColor="white" m={{ y: "2rem" }} textWeight="200">
€ {product.variants[0].price}
</Text>
<Button
rounded="lg"
shadow="3"
bg="black500"
m={{ y: "2rem" }}
onClick={() => addItemToCheckout(product.variants[0].id, 1)}
>Add to cart
</Button>
</Col>
</Row>
</ThemeProvider>
</Container>
))
return favoritesList;
};
export default FavoritesPage;
It wont load the fav component any thoughts greatly appreciated thanks
anything else that may be needed please ask thanks
**Nav-header.js**
The sidebar should not collapse when I am clicking outside of the page in react js. Please help with that. this is the code I wrote for the side menu. I am a beginner at the react js. Please help
import React, { useState } from 'react'
import * as FaIcons from 'react-icons/fa'
import * as AiIcons from 'react-icons/ai'
import { Link } from 'react-router-dom'
import { SidebarData } from './SidebarData'
import './Navbar.css'
import { IconContext } from 'react-icons'
const Header = () => {
const [sidebar, setsidebar] = useState(false)
const showSideBar = () => setsidebar(!sidebar)
return (
<>
<IconContext.Provider value={{ color: '#1D1D1D' }}>
<div className='navbar'>
<Link to="#" className='menu-bars'>
<FaIcons.FaBars onClick={showSideBar} color="#009540"/>
</Link>
<span className="meny">Meny</span>
</div>
<nav className={sidebar ? 'nav-menu active' : 'nav-menu' } >
<ul className='nav-menu-items' onClick={showSideBar}>
<li className='navbar-toggle'>
<Link to="#" className='menu-bars-logo'>
<AiIcons.AiOutlineClose/>
</Link>
</li>
{SidebarData.map((item, index) => {
return (
<li key={index} className={item.cName}>
<Link to={item.path}>
<span>{item.title}</span>
</Link>
</li>
)
})}
</ul>
</nav>
</IconContext.Provider>
</>
)
}
export default Header
You can use onblur, in react it would look like
return (
<div onBlur={() => {
... do something like setState
}}>
)
I've created a React NavBar following a tutorial, when I click the burger menu, the nav expands and collapses as expected, but when I click a link on the nav menu, it goes to the page but the nav bar doesn't collapse. I've checked a few questions/guides but they all link to Bootstrap and this code doesn't use Bootstrap, I'd rather not change the NavBar to Bootstrap if it can be avoided! Any help would be appreciated.
import React, { Component } from "react";
import logo from "../images/logo.svg";
import { FaAlignRight } from "react-icons/fa";
import { Link } from "react-router-dom";
export default class Navbar extends Component {
state = {
isOpen: false
};
handleToggle = () => {
this.setState({ isOpen: !this.state.isOpen });
};
componentDidMount() {
window.addEventListener("scroll", this.resizeHeaderOnScroll);
window.addEventListener("scroll", this.navTransparent);
window.addEventListener("scroll", this.navShadow);
};
resizeHeaderOnScroll() {
const distanceY = window.pageYOffset || document.documentElement.scrollTop,
shrinkOn = 100,
headerEl = document.getElementById("logo");
if (distanceY > shrinkOn) {
headerEl.classList.add("logoShrink");
} else {
headerEl.classList.remove("logoShrink");
}
}
navTransparent() {
const distanceY = window.pageYOffset || document.documentElement.scrollTop,
shrinkOn = 100,
headerEl = document.getElementById("navbar");
if (distanceY > shrinkOn) {
headerEl.classList.add("navbarBg");
} else {
headerEl.classList.remove("navbarBg");
}
}
navShadow() {
const distanceY = window.pageYOffset || document.documentElement.scrollTop,
shrinkOn = 100,
headerEl = document.getElementById("navbar");
if (distanceY > shrinkOn) {
headerEl.classList.add("navShadow");
} else {
headerEl.classList.remove("navShadow");
}
}
render() {
return <nav id="navbar">
<div className="nav-center">
<div className="nav-header">
<Link to="/">
<img id="logo" src={logo} alt="" />
</Link>
<button type="button" className="nav-btn" onClick={this.handleToggle}>
<FaAlignRight className="nav-icon" />
</button>
</div>
<ul className={this.state.isOpen ? "nav-links show-nav" : "nav-links"}>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/nigelservices">Services</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</div>
</nav>;
}
}
To answer the question here you can always do this because the Link component accepts the onClick prop:
export default class Navbar extends Component {
// Rest of your code
handleLinkClick = () => {
this.setState({ isOpen: false });
};
render() {
return (
// Your JSX
<Link to="/" onClick={handleLinkClick}>Home</Link>
)
}
}
Remember to add this in every link component.
As a side note you can also use the NavLink component in react router to handle the styling when the route is the current one. https://reactrouter.com/web/api/NavLink