npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-router-with-events

v0.2.0

Published

A custom React Wrapper Hook for Next.js App Router with Events

Downloads

2,249

Readme

useRouterWithEvent React/Next.js Hook

npm version npm downloads npm downloads npm bundle size npm bundle size npm license npm types

Description

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.

Installation

npm install use-router-with-events

Usage

- stopping the route change/ask the user for confirmation before leaving the page:

"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 (
    <div>
      ....
      <button className="..." onClick={handleClick}>
        Return to home page
      </button>
    </div>
  );
};

export default ContactPage;

notes:

  • the onRouteStart event should return a boolean value, if it returns false, the navigation will be canceled.
  • the onRouteStart event is triggered before the navigation starts.
  • the onRouterStart event is only triggered in the current scope, it will not be triggered in other components.

- add a logic after the router change:

"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 (
    <div>
      ...
      <button className="..." onClick={handleClick}>
        Learn more about me on the about page
      </button>
    </div>
  );
};

export default PortfolioPage;

- interrupt the Link component navigation form a child component:

// 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 (
    <div>
      <img
        src="https://eu.ui-avatars.com/api/?name=John+Doe&size=250"
        alt="avatar"
      />
    </div>
  );
};

export default Avatar;

// 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 (
    <div className="...">
      <ul className="...">
        {links.map(({ id, link, href }) => (
          <li
            key={id}
            className="..."
          >
            <Link href={href} onClick={(e) => {
              // we should prevent the default behavior of the link and use the router.push method to navigate to the page.
              e.preventDefault();
              router.push(href);
            }}>
              {link}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Navbar;

notes:

  • the on('routeStart') event should throw an error if the navigation should be canceled.
  • the on('routeStart') event is triggered before the navigation starts.
  • the off('routeStart') event should be called on the page/component unmount, otherwise, it will be triggered on every navigation.
  • we should prevent the default behavior of the link and use the router.push method to navigate to the page.

API

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<EventsTypes, Function | undefined>;
    on(event: EventsTypes, handler: Function): void;
    off(event: EventsTypes): void;
    emit(event: EventsTypes, ...args: any[]): void;
}

Example

check the full example in the [examples] folder

License

This project is licensed under the MIT License.

Contributing

Contributions are always welcome!, if you have any ideas, just open an issue and tell me what you think :) .