Reputation: 735
I'm toggling navigation in next.js, it works fine, it's just I want the navigation to close again when the route changes.
For example, if I'm on the home page and toggle the navigation, the navigation opens and shows a link to the about page. If I click that link, I get taken to the about page as expected - but the navigation is still open!
I've tried a few things - I think I want to utilize onRouteChangeComplete(url) but I'm struggling to update the navActive state.
My page.js file:
class Page extends Component {
state = {
navActive: false
};
toggle = () => {
this.setState({
navActive: !this.state.navActive
});
};
render() {
return (
<ThemeProvider theme={theme}>
<StyledPage className="full-page-wrapper">
<Meta />
<Header navActive={this.state.navActive} toggle={this.toggle} />
<PrimaryContent>{this.props.children}</PrimaryContent>
<GlobalStyle />
</StyledPage>
</ThemeProvider>
);
}
}
Then my header file:
class Header extends React.Component {
render() {
return (
<HeaderSide
<HeaderToggleBar onClick={() => this.props.toggle()} />
</HeaderSide>
);
}
}
So the app starts off with navActive state of false, clicking the HeaderToggleBar element opens and closes the nav. But I need to close the nav when the route changes. I guess I could put the click event on the navigation items within the header (so clicking to a new page toggles) but that seems a bit over the top.
Thank you.
Upvotes: 48
Views: 76846
Reputation: 131
For the latest Next 14+ we have next/navigation
now for app router in order to detect & do something on client components (Only on client components) we can use this as it is in documentation of next js
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
function ExampleClientComponent() {
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
// Do something here...
}, [pathname, searchParams])
}
Upvotes: 0
Reputation: 493
For Nextjs 13 +
'use client';
import { useEffect } from 'react';
import { useSelectedLayoutSegment } from 'next/navigation';
const activeSegment = useSelectedLayoutSegment();
useEffect(() => {
console.log('>>>>>');
}, [activeSegment]);
Upvotes: 3
Reputation: 61
Here is how we can detect page changes in Next.js 13 App folder.
You can listen for page changes by composing other Client Component hooks like
usePathname
anduseSearchParams
.useSearchParams } from 'next/navigation' export function NavigationEvents() { const pathname = usePathname() const searchParams = useSearchParams() useEffect(() => { const url = `${pathname}?${searchParams}` console.log(url) // You can now use the current URL // ... }, [pathname, searchParams]) return null } ```
Link of the doc: useRouter docs
It works for me.
Upvotes: 6
Reputation: 959
if you're using a functional React component, you can do this:
const router = useRouter();
useEffect(() => {
if (isMenuOpen) {
setMenuOpen(!isMenuOpen);
}
}, [router.asPath]);
Upvotes: 53
Reputation: 49182
when route changing is happening, next.js is trying to load the other page. A better way to handle the route change, if you are tracking the loading state, keep that loading state true, until the route change is complete.
const router = useRouter();
useEffect(() => {
const handleComplete = () => {
setIsLoading(false);
};
router.events.on("routeChangeComplete", handleComplete);
router.events.on("routeChangeError", handleComplete);
return () => {
router.events.off("routeChangeComplete", handleComplete);
router.events.off("routeChangeError", handleComplete);
};
}, [router]);
Upvotes: 4
Reputation: 5484
Complete example
Using a functional component.
Triggering on routeChangeStart
, so the menu can optionally be animated immediately after clicking.
import { useRouter } from 'next/router';
const Menu = () => {
const router = useRouter();
const [show, setShow] = useState(false);
const hide = useCallback(() => {
setShow(false);
}, [setShow]);
useEffect(() => {
// subscribe
router.events.on('routeChangeStart', hide);
// unsubscribe
return () => router.events.off('routeChangeStart', hide);
}, [hide, router.events]);
return show ? <div>menu</div> : null
}
Managing nesting (TypeScript)
I needed something like this in top level, though I'd share:
interface ContextProps {
show: boolean;
setShow: (show: boolean) => void;
};
export const MenuContext = React.createContext<Partial<ContextProps>>({
show: false,
setShow: () => {},
});
const [show, setShow] = useState(false);
return (
<MenuContext.Provider value={{ show, setShow }}>
{children}
</MenuContext.Provider>
)
Upvotes: 6
Reputation: 19
Reference: https://nextjs.org/docs/api-reference/next/router
There you can see class component base code. But here I use the functional components to solve this issue.
useEffect(() => {
const handleRouteChange = () => {
setOpenDropdown(false)
}
router.events.on('routeChangeStart', handleRouteChange)
}, [])
Upvotes: 1
Reputation: 529
We can use router events to handle this. Refer: https://nextjs.org/docs/api-reference/next/router#usage-6
const router = useRouter();
useEffect(() => {
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
};
}, [router.events]);
Upvotes: 16
Reputation: 5481
Have a look at https://github.com/zeit/next.js/#router-events. You should be fine with sth like this in the ctor of your Header component:
constructor(props){
super(props);
Router.events.on('routeChangeComplete', (url) => {
props.toggle();
});
}
Upvotes: 22
Reputation: 47
I believe you need to bind your callback. You might also need to add a constructor. Here's an snippet from a project that accepts file uploads.
class Files extends React.Component {
// define your constructor and call super before setting state vars
constructor(props){
super(props);
this.state = {
uploadStatus: false
}
// Bind your callback
this.handleUploadImage = this.handleUploadImage.bind(this);
// set other initial vars if needed
}
handleUploadImage(files){
// do stuff with the files
}
render () {
return (
<div id="fileContainer">
<MyDropzone id={this.props.project._id} onDrop={this.handleUploadImage}>
{({getRootProps}) => <div {...getRootProps()} />}
</MyDropzone>
</div>
)
}
}
Upvotes: 0