In my nextjs-app I have a Navbar with some navitems:
Navbar.tsx:
const Navbar = ({ navitems }) => {
return (
<div>
{navitems?.map((navitem, idx) => (
<NavItem key={idx} navitem={navitem} />
))}
</div>
)
}
export default Navbar
NavItem.tsx:
import Link from "next/link"
import { useRouter } from "next/router"
const NavItem = ({navitem}) => {
const router = useRouter()
return (
<div>
<Link href={"/" + navitem.url.slug}
className={`${ router.query.slug === navitem?.url?.slug ? "active " : ""}`}>
{navitem.title}
</Link>
</div>
)
}
export default NavItem
So far so good. When I'm the route /about the menu item has the active-class.
How can I add the active class when I have dynamic routes like for example /about/employees - so that the Navitem "about" still has the active class?
How can I achieve that?
Related
I was using Link to navigate to new page, now I made one page of all components and it's not doing anything on click.
import React, { useState } from 'react'
import cn from 'classnames'
// import Link from 'next/link'
import { Link } from "react-scroll"
import Logo from '../Logo/Logo'
import styles from './Layout.module.scss'
interface ILayoutProps {
children: React.ReactNode
}
export default function Layout({ children }: ILayoutProps) {
const [activeTab, setActiveTab] = useState('Home')
const navigation = ['#Home', '#About', '#Portfolio', '#Contact']
console.log(activeTab);
return (
<div>
<nav className={styles.navContainer}>
<Link to={'/#Home'}>
<Logo />
</Link>
<ul className={styles.navItems}>
{navigation.map((nav, index) => {
return (
<li key={index}>
<Link
to={`/${nav === '#Home' ? '/' : nav}`}
className={cn(styles.linkItem, {
[styles.activeTab]: activeTab === nav
})}
onClick={() => {
setActiveTab(nav)
console.log(nav)
}}
spy={true}
smooth={true}
offset={50}
duration={500}
>
{nav.slice(1)}
</Link>
</li>
)
})}
</ul>
<a className={styles.button} href='assets/Stas_Gavrilov_resume.txt' download>Resume</a>
</nav>
<main>{children}</main>
</div>
)
}
I follow up with the docs on react-scroll but it did not helped to solve my issue :(
It's saying it can't target the section element:
react_devtools_backend.js:4012 target Element not found
You need to remove the / on the to prop in the Link component since the id of the element you want to scroll to has not the id /#Home but #Home.
<Link
to={`${nav === "#Home" ? "/" : nav}`} // here
...
>
Instead of
<Link
to={`/${nav === "#Home" ? "/" : nav}`}
...
>
Note that the id needs to match so the elements you want to scroll to must have the exact id.
Since your list of ids is
const navigation = ["#Home", "#About", "#Portfolio", "#Contact"];
The id of the elements need to contain the #
<>
<section id="#Home">Home</section>
<section id="#About">About</section>
<section id="#Portfolio">Portfolio</section>
<section id="#Contact">Contact</section>
</>
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;
I've created a Breadcrumb component but I'm struggling to add routing to it.
So far, the component is able to load a custom number of nodes based on how many elements we send to an array and it logs into the console the name of the node when clicked.
What it is needed is to make this breadcrumb change the url when a node is clicked.
It should contain something like this:
<Switch>
<Route path="/component0" component={Component0} />
<Route path="/component1" component={Component1} />
...
</Switch>
Is it possible to make it for a custom number of nodes?
This is the code so far:
Parent component:
import React, { useState } from 'react';
import Breadcrumbs from './Breadcrumbs';
export interface BreadcrumbProps {}
export function Breadcrumb(props: BreadcrumbProps) {
const [crumbs, setCrumbs] = useState(['Home', 'Category', 'Sub Category']);
const selected = (crumb: any) => {
console.log(crumb);
};
return (
<div>
<Breadcrumbs crumbs={crumbs} selected={selected} />
</div>
);
}
export default Breadcrumb;
Child component:
function Breadcrumbs(props: any) {
function isLast(index: number) {
return index === props.crumbs.length - 1;
}
return (
<nav className=''>
<ol className=''>
{props.crumbs.map((crumb, ci) => {
const disabled = isLast(ci) ? 'disabled' : '';
return (
<li key={ci} className=''>
<button className={`btn btn-link ${disabled}`} onClick={() => props.selected(crumb)}>
{crumb}
</button>
</li>
);
})}
</ol>
</nav>
);
}
export default Breadcrumbs;
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.
I have a navigation component with a list containing links. I need to run a function when these are clicked in addition to linking(going to specific page). What I have so far is working except if user clicks on link to active page.
How can I modify my code so my function doesn't run and link is disable if user is already on that page?
Here is what I have:
class HeaderMenu extends Component {
navFunc = () => {
//some functionality
}
render() {
return (
<div>
<div className="sidebar">
<ul className="header">
<li onClick={this.navFunc}>
<Link href="/index">
<a>home</a>
</Link>
</li>
<li onClick={this.navFunc}>
<Link href="/about">
<a>about</a>
</Link>
</li>
<li onClick={this.navFunc}>
<Link href="/other">
<a>other</a>
</Link>
</li>
</ul>
</div>
</div>
)
}
}
You can create an ActiveLink component that will skip the push action when clicked.
This ActiveLink component is based on the example in the next.js documentation (scroll up to see it), and I have not tested it.
import { withRouter } from 'next/router'
const ActiveLink = ({ children, router, href }) => {
const active = router.pathname === href
const style = {
marginRight: 10,
color: active ? 'red' : 'black'
}
const handleClick = (e) => {
e.preventDefault()
active || router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
export default withRouter(ActiveLink)