A custom React Wrapper Hook for Next.js App Router with Events
npm install use-router-with-events

!npm downloads




Since the release of Next.js 13, which introduced the App directory, a new useRouter hook from next/navigation was provided for handling routing. However, this new useRouter hook lacks the events that were available in the previous useRouter hook from next/router that is used in the pages directory, which is crucial for certain use cases. Consequently, this package was developed to add those events to the new useRouter hook, ensuring similar behavior and API to the old useRouter hook.
This package provides a hook called useRouterWithEvents, which is compatible with both Next.js 13 and Next.js 14.
``bash`
npm install use-router-with-events
`tsx
"use client";
import useRouterWithEvents from "use-router-with-events";
const ContactPage = () => {
const router = useRouterWithEvents();
const handleClick = () => {
// ask the user if they are sure they want to leave the page,
// onRouterStart event will be triggered, if it returns false, the navigation will be canceled.
router.events.onRouteStart = () => {
/**
* here you can add your custom logic, for example, show a modal to ask the user if they are sure they want to leave the page.
* the logic should return a boolean value, if it returns false, the navigation will be canceled.
*/
return confirm("Are you sure you want to leave this page?");
};
// navigate to the home page
router.push("/");
};
return (
export default ContactPage;
`
#### notes:
- the onRouteStart event should return a boolean value, if it returns false, the navigation will be canceled.onRouteStart
- the event is triggered before the navigation starts.onRouterStart
- the event is only triggered in the current scope, it will not be triggered in other components.
---
`tsx
"use client";
import useRouterWithEvents from "use-router-with-events";
const PortfolioPage = () => {
const router = useRouterWithEvents();
const handleClick = () => {
// we can use the onRouteComplete event to track the route change after the navigation is completed,
// for example, we can send an event to Google Analytics.
router.events.onRouteComplete = () => {
/**
* here you can add your custom logic, for example, send an event to Google Analytics.
*
*/
(window as any).gtag = {
event: "page_view",
data: {
page_path: "/about",
page_title: "About Page",
send_to: "UA-XXXXX-Y",
},
};
};
// navigate to the about page
router.push("/about");
// we can test the logic by checking the window.gtag in the console.
};
return (
export default PortfolioPage;
`
---
`tsx
// ChildComponent.tsx
import React, { useEffect } from "react";
import useRouterWithEvents from "use-router-with-events";
/**
* Note: this example won't work if we didn't add onClick event to the Link in the Navbar component,
* please check /src/app/components/Navbar.tsx
*/
const Avatar = () => {
const router = useRouterWithEvents();
/**
* here we can interrupt the route from child component,
* for example, we can show a modal to confirm the navigation.
* in this case, we have to register the RouteStart event in the router instance,
* the event should throw an error if the navigation should be canceled.
*/
useEffect(() => {
// listen if routeStart event is emitted
router.events.on("routeStart", () => {
const result = confirm("Are you sure you want to leave this page?");
if (!result) {
// throw an error to cancel the navigation
throw new Error("Navigation canceled");
}
});
return () => {
// remove the routeStart event listener on the page/component unmount, otherwise, it will be triggered on every navigation.
router.events.off("routeStart");
};
}, []);
return (
export default Avatar;
`
`tsx
// NavbarComponent.tsx
'use client';
import Link from "next/link";
import React, { useState } from "react";
import useRouterWithEvents from "use-router-with-events";
const Navbar = () => {
const [nav, setNav] = useState(false);
const router = useRouterWithEvents();
const links = [...];
return (
export default Navbar;
`
#### notes:
- the on('routeStart') event should throw an error if the navigation should be canceled.on('routeStart')
- the event is triggered before the navigation starts.off('routeStart')
- the event should be called on the page/component unmount, otherwise, it will be triggered on every navigation.router.push
- we should prevent the default behavior of the link and use the method to navigate to the page.
---
`typescript
useRouterWithEvents(): RouterWithEvents;
type RouterWithEvents = AppRouterInstance & {
events: EventsHandler & {
onRouteStart?: () => boolean;
onRouteComplete?: () => void;
};
};
type EventsTypes = "routeStart" | "routeComplete" | "routeError";
declare class EventsHandler {
private static _instance;
private constructor();
static get getInstance(): EventsHandler;
handlers: Record
on(event: EventsTypes, handler: Function): void;
off(event: EventsTypes): void;
emit(event: EventsTypes, ...args: any[]): void;
}
``
check the full example in the [examples] folder
This project is licensed under the MIT License.
Contributions are always welcome!, if you have any ideas, just open an issue and tell me what you think :) .