Close Hamburger Menu When mobile menu Link is clicked - javascript

when I click the menu item the page loads but the menu stays open.
this is the Mobile Menu it JSX:
const MobileMenu = () => {
const navigation = [
{ link: '/applications', text: 'Applications' },
{ link: '/multi-media', text: 'Media' },
{ link: '/websites', text: 'Websites' },
{ link: '/mobile-apps', text: 'Mobile' },
{ link: '/support', text: 'Support' },
{ link: '/contact', text: 'Contact' },
{ link: '/', text: 'Login' },
];
return (
<NavMenuContainer>
<NavList>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar id="mobile-search" />
</div>
{navigation.map(nav => (
<NavLink key={nav.text}>
<Link href={nav.link}><a>
{nav.text}
</a></Link>
</NavLink>
))}
</NavList>
</NavMenuContainer>
);
};
export default MobileMenu
this is NAV the MobileMenu menu is in(JSX:
export default function HamburgerMenu(props) {
const [isOpen, setOpen] = useState(false);
//change
const toggleMenu = () => {
let dd = document.body;
dd.classList.toggle("navbar-mobile");
setOpen(!isOpen);
};
return (
<Theburger>
<HamburgerMenuContainer>
<MenuToggle toggle={toggleMenu} isOpen={isOpen} />
<MenuContainer
initial={false}
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
transition={menuTransition}
>
<ContentContainer>
<MobileMenu isOpen={isOpen} />
</ContentContainer>
</MenuContainer>
</HamburgerMenuContainer>
</Theburger>
);
}
this is the website main menu it TSX:
const Navbar: FC<NavbarProps> = ({ links }) => (
<NavbarRoot>
<Container>
<div className={s.nav}>
<div className="flex items-center">
<Link href="/"><a>
<div className="logo">
<Image width={106} height={27} src="/logo.svg" alt="brand" />
</div>
</a></Link>
<nav className={s.navMenu}>
{[...links3 ].map((page) => (
<span key={page.url}>
<Link href={page.url!}>
<a className={s.link}>
{page.name}
</a>
</Link>
</span>
))}
{links?.map((l) => (
<Link href={l.href} key={l.href}>
<a className={s.link}>{l.label}</a>
</Link>
))}
</nav>
</div>
<div className="flex items-center justify-end flex-1 space-x-8 mr-5">
<UserNav />
</div>
<div className="flex items-center justify-end flex-2">
<Nav />
</div>
</div>
</Container>
</NavbarRoot>
)
export default Navbar
Its a nextjs app Im using a Layout component in _app.tsx not sure if that matters but it really shouldn't, I Missed tsx with jsx and according to the docs at NextJS and javascript in general mixing them shouldn't cause problems.

You're missing to give the state as a prop to your toggle menu function.
Thus isOpen is always false and the state gets changed always to true.
Change your toggleMenu() to toggleMenu(isOpen) and it's fine.
export default function HamburgerMenu(props) {
const [isOpen, setOpen] = useState(false);
//change
const toggleMenu = (myState) => {
let dd = document.body;
dd.classList.toggle("navbar-mobile");
setOpen(!isOpen);
};
return (
<Theburger>
<HamburgerMenuContainer>
<MenuToggle toggle={()=>toggleMenu(isOpen)} isOpen={isOpen} />
<MenuContainer
initial={false}
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
transition={menuTransition}
>
<ContentContainer>
<MobileMenu isOpen={isOpen} />
</ContentContainer>
</MenuContainer>
</HamburgerMenuContainer>
</Theburger>
);
}
The reason you're running into the menu being always visible, is because when react compiles the function, the initial value is used whether it was changed or not. Resulting in the following function to always console log "initial" even when the state has changed.
function A() {
const [textState, setTextState] = useState('initial');
const printState = () => {
console.log(textState);
setTextState('changed');
}
return <button onClick={()=>printState()}>Print me</button>
}
this behaves different in the following two scenarios where either the state is from the parent or the props are given to the function.
Parent Props
function B({textState, setTextState}) {
const printState = () => {
console.log(textState);
setTextState('changed');
}
return <button onClick={()=>printState()}>Print me</button>
}
In function B the printState function is given as a prop and the function is rerendered when the props change, causing also the printState function to be compiled again with the new props causing to console log changed instead of initial.
The other option is handling the state in the component itself and giving the state as a prop to our function.
function C() {
const [textState, setTextState] = useState('initial');
const printState = (prop) => {
console.log(prop);
setTextState('changed');
}
return <button onClick={()=>printState(textState)}>Print me</button>
}
Here the prop is given directly to the printState function, while the printState function is not being recompiled the updated state is given as a prop and handled accordingly

Related

How to clone/copy events in react.js

I have created a component which generates a Modal Dialog. As you may know, modal must be placed inside root (body) element as a child to defuse any parent styles.
To accomplish the process above, I use vanilla js to clone my Modal component and append it to body like so:
useEffect(() => {
const modalInstance = document.getElementById('modal-instance-' + id);
if (modalInstance) {
const modal = modalInstance.cloneNode(true);
modal.id = 'modal-' + id;
const backdrop = document.createElement('div');
backdrop.id = 'modal-backdrop';
backdrop.className = 'hidden fixed top-0 bottom-0 start-0 end-0 bg-black bg-opacity-75 z-[59]';
backdrop.addEventListener('click', toggleModal);
document.body.appendChild(backdrop);
document.body.appendChild(modal);
const closeBtn = document.querySelector(`#modal-${id} > [data-close='modal']`);
closeBtn.addEventListener('click', toggleModal);
}
So far so good and Modal works perfectly; but problems start showing up when I pass elements with events as children to my Modal component.
<Modal id='someId' size='lg' show={showModal} setShow={setShowModal} title='some title'>
<ModalBody>
Hellowwww...
<Button onClick={() => alert('working')} type='button'>test</Button>
</ModalBody>
</Modal>
The above button has an onClick event that must be cloned when I clone the entire modal and append it to body.
TL;DR
Is there any other way to accomplish the same mechanism without vanilla js? If not, how can I resolve the problem?
You should use the createPortal API from ReactDom
https://beta.reactjs.org/apis/react-dom/createPortal
function Modal (props) {
const wrapperRef = useRef<HTMLDivElement>(null);
useIsomorphicEffect(() => {
wrapperRef.current = document.getElementById(/* id of element */)
}, [])
return createPortal(<div>/* Modal content */ </div>, wrapperRef )
}
The useIsomorphic effect hook is export const useIsomorphicEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect;
Because of " Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format."
After spending some time searching a way to resolve this, I found out that the whole process that I went through was wrong and apparently there's a more robust way to accomplish this.
So, here is my final working component in case you need to learn or use it in your projects:
import {useEffect, useState} from 'react';
import Button from '#/components/Button';
import {X} from 'react-bootstrap-icons';
import {createPortal} from 'react-dom';
export const Modal = ({id, title, className = '', size = 'md', show = false, setShow, children}) => {
const [domReady, setDomReady] = useState(false);
const sizeClass = {
sm: 'top-28 bottom-28 start-2 end-2 sm:start-28 sm:end-28 sm:start-60 sm:end-60 xl:top-[7rem] xl:bottom-[7rem] xl:right-[20rem] xl:left-[20rem]',
md: 'top-16 bottom-16 start-2 end-2 xl:top-[5rem] xl:bottom-[5rem] xl:right-[10rem] xl:left-[10rem]',
lg: 'top-2 bottom-2 start-2 end-2 sm:top-3 sm:bottom-3 sm:start-3 sm:end-3 md:top-4 md:bottom-4 md:start-4 md:end-4 lg:top-5 lg:bottom-5 lg:start-5 lg:end-5',
};
useEffect(() => {
setDomReady(true);
}, []);
return (
domReady ?
createPortal(
<>
<div className={`${show ? '' : 'hidden '}fixed top-0 bottom-0 start-0 end-0 bg-black bg-opacity-75 z-[59]`} onClick={() => setShow(false)}/>
<div id={id}
className={`${show ? '' : 'hidden '}fixed ${sizeClass[size]} bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-200 drop-shadow-lg rounded-lg z-[60] ${className}`}>
<Button
className='absolute top-3 end-3'
type='button'
size='sm'
color='secondaryOutlined'
onClick={() => setShow(false)}
><X className='text-xl'/></Button>
{title && <div className='absolute top-4 start-3 end-16 font-bold'>{title}</div>}
<div>{children}</div>
</div>
</>
, document.getElementById('modal-container'))
: null
);
};
export const ModalBody = ({className = '', children}) => {
return (
<div className={`mt-10 p-3 ${className}`}>
<div className='border-t border-gray-200 dark:border-gray-600 pt-3'>
{children}
</div>
</div>
);
};
Usage:
_app.js:
<Html>
<Head/>
<body className='antialiased' dir='rtl'>
<Main/>
<div id='modal-container'/> <!-- Pay attention to this --!>
<NextScript/>
</body>
</Html>
Anywhere you need modal:
<Modal id='someId' size='lg' show={showModal} setShow={setShowModal} title='Some title'>
<ModalBody>
Hellowwww...
<Button onClick={() => alert('working')} type='button'>Test</Button>
</ModalBody>
</Modal>
I should mention that I use tailwindcss library to style my modal and react-bootstrap-icons for icons.

Why my Hoc component for PopUp doestWork?

Hoc for popup does not work. Hock has a boolean state and two functions that change it. I pass them to the popup opening buttons and to the popup itself. The state changes through the console, but the popup itself does not open.
My HOC component
const Hoc = (Component) => {
const HandleChange = () => {
const [active, setActive] = useState(false);
function open() {
setActive(true)
}
function close() {
setActive(false)
}
useEffect(() => {
console.log(active)
}, [active])
return (
<Component activevvv={active} open={open} close={close} />
)
}
return HandleChange;
}
export default Hoc;
And my Button,the button works well, when you click on it, the state changes to true, I see this in the console log.
function ButtonsOur({ open }) {
return (
<div className="base__routes__button">
<Buttons className={"base__button__moreInf open-popup-exc"} Click={open} >Докладніше</Buttons>
<Buttons className={"base__button__moreInf open-popup-exc"} Click={open} >Забронювати</Buttons>
</div>
)
}
export default Hoc(ButtonsOur);
And my PopU. I pass the state of the current state to this component, and if the state is true, the popAp will be displayed. But nothing happens, although the state in the hockey changes.
function PopUpExc({activevvv, close}) {
return (
<div className={activevvv ? "popup__bg__exc active" : "popup__bg__exc"}>
<div className="popup__exc" onClick={e => e.stopPropagation()}>
<img onClick={close} src={Close} alt="close-popup" className="close-popup_exc" />
<div className="container">
<div className="row">

Toggle a class only to one element at each click of the mouse - React hooks

I'm trying to add and remove a class when clicking on an item of my header, but I struggle to do it and I don't know how to map the rendered items in the header component.
Here's the first part of the code with a function that works for routing and window.location.
I'm able to add the class but it gets added to each element clicked and it gets removed only when I click again on it.
import React, { useState } from 'react';
const Link = ({ href, children }) => {
const [activeItem, setActiveItem] = useState(false);
const onClick = (event) => {
if (event.metaKey || event.ctrl) {
return;
}
event.preventDefault();
window.history.pushState({}, '', href);
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
setActiveItem(!activeItem);
};
return (
<a
onClick={onClick}
className={`item ${activeItem ? 'active' : ''}`}
href={href}
>
{children}
</a>
);
};
export default Link;
Here's my header element instead:
import React from 'react';
import Link from './Link';
import Logo from './Logo';
const Header = () => {
return (
<div className="ui secondary pointing menu">
<Link href="/">
<Logo />
</Link>
<div className="pointing right menu">
<Link href="/services">services</Link>
<Link href="/works">works</Link>
<Link href="/contacts">contacts</Link>
</div>
</div>
);
};
export default Header;
You need to make your link components aware of each other by lifting the state to your header component. Then you pass you tell your link components which link is currently selected by passing it as a prop and you also need to give them the ability to change which link is currently selected:
import React from 'react';
import Link from './Link';
import Logo from './Logo';
const Link = ({ href, children, isActive, handleClick }) => {
const onClick = (event) => {
if (event.metaKey || event.ctrl) {
return;
}
event.preventDefault();
window.history.pushState({}, '', href);
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
handleClick();
};
return (
<a
onClick={onClick}
className={`item ${isActive ? 'active' : ''}`}
href={href}
>
{children}
</a>
);
};
export default Link;
const Header = () => {
const [activeLink, setActiveLink] = useState(0)
return (
<div className="ui secondary pointing menu">
<Link
href="/"
isActive={activeLink === 0}
handleClick={() => setActiveLink(0)}
>
<Logo />
</Link>
<div className="pointing right menu">
<Link
href="/services"
isActive={activeLink === 1}
handleClick={() => setActiveLink(1)}
>
services
</Link>
<Link
href="/works"
isActive={activeLink === 2}
handleClick={() => setActiveLink(2)}
>
works
</Link>
<Link
href="/contacts"
isActive={activeLink === 3}
handleClick={() => setActiveLink(3)}
>
contacts
</Link>
</div>
</div>
);
};
export default Header;

Modal - Can't make a condition work with a prop

Im trying to make a Modal and when someone clicks to open it I want to disable scrollbar.
My Modal is a component and I cant pass the prop "open" to the condition. When someone clicks to open the Modal the condition doesn't work and the scrollball stays.
My Dialog.js is where I have my array and my functions, I pass them as props to the others components, to each individual Modal.
Dialog.js
export default function Dialog() {
let [Dentisteria, setDentisteria] = useState(false);
let [Endodontia, setEndodontia] = useState(false);
let [Ortodontia, setOrtodontia] = useState(false);
const dataEspecialidades = [
{
setOpen: setDentisteria,
open: Dentisteria,
},
{
setOpen: setEndodontia,
open: Endodontia,
},
{
id: 3,
setOpen: setOrtodontia,
open: Ortodontia,
},
];
return (
<>
<div className="grid gap-8 mx-auto md:grid-cols-3">
{dataEspecialidades.map((item) => {
return (
<>
<Card setOpen={item.setOpen}>
<CardTitle>{item.title}</CardTitle>
<CardDescription>{item.text}</CardDescription>
</Card>
<Modal setOpen={item.setOpen} open={item.open}>
<ModalTitle>{item.title}</ModalTitle>
<ModalDescription>
{item}
</ModalDescription>
</Modal>
</>
);
})}
</div>
</>
);
}
My Card component is used to open the Modal and its working. I pass the prop
setOpen that I have in my Dialog.js.
Card.js
export function Card({ setOpen, children }) {
return (
<>
<div
onClick={() => setOpen(true)}
className="px-4 py-6 text-center rounded-lg cursor-pointer select-none bg-gradient-to-br from-white to-neutral-50 drop-shadow-lg"
>
{children}
</div>
</>
);
}
My Modal component is used to show and close the Modal and its working. I pass the prop setOpen and open that I have in my Dialog.js.
But the open prop is not working in the condition to hide the scrollbar when the Modal is open.
Modal.js
export function Modal({ open, setOpen, children }) {
if (typeof document !== "undefined") {
if (open) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
}
return (
<>
<div
className={`${open ? "" : "hidden"} fixed z-10 inset-0 overflow-y-auto`}
>
<div className="flex items-center justify-center min-h-screen p-4">
<div className="fixed inset-0 bg-black opacity-30"></div>
<div className="relative w-full max-w-2xl p-8 mx-auto bg-white rounded-lg">
{children}
</div>
</div>
</div>
</>
);
}
You are not tracking open with a state, you could use the useEffect hook for this
https://reactjs.org/docs/hooks-effect.html
const [modalIsOpen, setmodalIsOpen] = useState(open);
useEffect(() => {
// Update the body style when the modalIsOpenState changes
if (modalIsOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
}, [modalIsOpen]); // adding this will run useEffect any time modalIsOpen changes see the "Tip: Optimizing Performance by Skipping Effects" part of the documentation for more details
I realise your question is for next.js. I'm used to using React myself, you can use my answer in your Next.js application by importing useEffect like this
import React, { useState, useEffect } from 'react'

How to Change a css property based on a state of another component

I'm building a web page with gatsby which is based in react, and I need my nav component changes his sticky position to relative or auto, every time that I open the modal of the gallery component..but I don't know how to approach and solve the problem. The nav component belongs to the layout component which is Gallery's parent component...Here are the components involved:
nav component:
import React, { Component } from 'react'
import { Location } from '#reach/router'
import { Link } from 'gatsby'
import { Menu, X } from 'react-feather'
import Logo from './Logo'
import './Nav.css'
export class Navigation extends Component {
state = {
active: false,
activeSubNav: false,
currentPath: false
}
componentDidMount = () =>
this.setState({ currentPath: this.props.location.pathname })
handleMenuToggle = () => this.setState({ active: !this.state.active })
// Only close nav if it is open
handleLinkClick = () => this.state.active && this.handleMenuToggle()
toggleSubNav = subNav =>
this.setState({
activeSubNav: this.state.activeSubNav === subNav ? false : subNav
})
render() {
const { active } = this.state,
{ subNav } = this.props,
NavLink = ({ to, className, children, ...props }) => (
<Link
to={to}
className={`NavLink ${
to === this.state.currentPath ? 'active' : ''
} ${className}`}
onClick={this.handleLinkClick}
{...props}
>
{children}
</Link>
)
return (
<nav className={`Nav ${active ? 'Nav-active' : ''}`}>
<div className="Nav--Container container">
<Link to="/" onClick={this.handleLinkClick}>
<div style={{ width: `40px`, margin:`0 20px`}}>
<Logo />
</div>
</Link>
<div className="Nav--Links">
<NavLink to="/">Home</NavLink>
<NavLink to="/contact/">Contacto</NavLink>
<div className={`Nav--Group ${this.state.activeSubNav === 'about' ? 'active' : '' }`} >
<span className={`NavLink Nav--GroupParent ${
this.props.location.pathname.includes('about') ||
this.props.location.pathname.includes('team') ||
this.props.location.pathname.includes('news')
? 'active'
: ''
}`}
onClick={() => this.toggleSubNav('about')}
>
Nosotros
</span>
<div className="Nav--GroupLinks">
{subNav.map( (link, index)=> (
<NavLink
to={link.link}
key={'posts-subnav-link-' + index}
className="Nav--GroupLink">{link.name}</NavLink>
))}
</div>
</div>
</div>
<button
className="Button-blank Nav--MenuButton"
onClick={this.handleMenuToggle}
>
{active ? <X /> : <Menu />}
</button>
</div>
</nav>
)
}
}
export default ({ subNav }) => (
<Location>{route => <Navigation subNav={subNav} {...route} />}</Location>
)
the default position property is set to sticky in the nav.css file I want remove that and change it
dynamically depending of the modal gallery state, open or close.
this is my gallery component:
import React, { useState, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
const PhotoGallery = ({photos}) => {
const [currentImage, setCurrentImage] = useState(0);
const [viewerIsOpen, setViewerIsOpen] = useState(false);
const openLightbox = useCallback((event, { photo, index }) => {
setCurrentImage(index);
setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
setCurrentImage(0);
setViewerIsOpen(false);
};
return(
<div>
<Gallery photos={photos} onClick={openLightbox} />
<ModalGateway>
{viewerIsOpen ? (
<Modal onClose={closeLightbox}>
<Carousel
currentIndex={currentImage}
views={photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
)
}
export default PhotoGallery
the problem is that when the modal is open the nav still sticky and does not allow me access to the modal controls, like close and expand...and I need to change that.
There are a few approaches to this.
Old school classname toggling
Pass a prop down to the child component that reflects the state. On the child, use that prop to conditionally render one or more classes that represent the desired presentation.
Assign styles via style prop
This is similar to #1, but eliminates a layer of abstraction. Instead of assembling a class list you just assemble the CSS styles you'd like to apply as an object.
const Component = ({ someState }) =>
<div style={someState ? { border: "5px solid red" } : { color: "#999" }}>
Some Text
</div>
Use a CSS-in-JS library
The downside of the above approach is that you wind up duplicating styles for each instance of your element on the page. CSS-in-JS libraries solve this by extracting your styles into an automatically generated class and applying the class to your component instead. I prefer Emotion, but there are others.
Using Emotion you're able to accept a className prop from the parent that override the defaults set by the child. This inversion-of-control is really powerful and solves many of the shortcomings with early CSS-in-JS approaches.
const ParentComponent = () => {
const [someState] = useState(false)
return <ChildComponent css={{ color: someState ? "blue" : "red" }} />
}
const ChildComponent = ({ className }) =>
<div
css={{
color: "#000",
border: "4px solid currentColor"
}}
className={className}
>
Some Text
</div>
In the above example, className is assigned by Emotion using the generated class name assigned based on the css prop passed to ChildComponent inside of ParentComponent. The result of this would be a div with a blue border and blue text when someState is false (default). When someState is switched to true, the border and text will be red. This is because the styles passed in via className will override the styles assigned directly via css in Emotion.

Categories