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

next-router-helpers

v0.1.0

Published

Wrapper around the Next.js router that allows for mocking and makes it easier to use routes with parameters.

Downloads

3

Readme

next-router-helpers

Wrapper around the Next.js router that allows for mocking and makes it easier to use routes with parameters.

  • Easily mock the Next router in tests and in Storybook
  • Unit test components that uses the Next.js router
  • Navigate to parametized routes
import { useRouter } from "next-router-helpers";

function MyComponent() {
  const { pushRoute } = useRouter();

  function onClick() {
    pushRoute({
      pathname: "/posts/[id]",
      query: {
        id: "10",
      },
    });
  }

  return <button onClick={onClick}>Navigate</button>;
}


Install

yarn add next-router-provider

Why?

The Next.js router isn't easy to mock in tests. There's no API for creating a mock router and there is no context provider you can use in tests to make the useRouter from next/router work.

Navigating to routes with params means you need to pass in both the Url and as parameters into router.push and router.replace.

router.push(
  {
    pathname: "/posts/[id]",
    query: {
      id: "2",
    },
  },
  {
    pathname: "/posts/2",
    query: {
      id: "2",
    },
  },
);

This library simplifies it down to:

pushRoute({
  pathname: "/posts/[id]",
  query: {
    id: "10",
  },
});

Which makes it easier for you to build up helpers to generate routes:

import { postsRoute } from "./lib/routes";

pushRoute(postsRoute("2"));

Or using custom hooks to create links to routes:

function usePostsRoute(id: string) {
  const { createLink } = useRouter();
  return createLink({
    pathname: "/posts/[id]",
    query: {
      id,
    },
  });
}
const { href, onClick } = usePostsRoute("2");

Which eliminates most of the need for <Link>.

Setup

You need to add the <RouterProvider> to your pages/_app file:

import App from "next/app";
import React from "react";
import { RouterProvider } from "next-router-provider";

export default class NextApp extends App {
  render(): JSX.Element {
    const { Component } = this.props;
    return (
      <RouterProvider>
        <Component />
      </RouterProvider>
    );
  }
}

Usage

Once you've setup the provider you can use hooks in your components:

  • useRouter: A replacement for the useRouter from next/router.
  • useQuery: Get a query param from the URL.
  • useRouteParam: Get a route param from the URL. This will throw if it doesn’t exist.
  • usePrefetch: Prefetch the assets for a route.

In your components you can access the router. Here's a (mostly pointless) example that shows a few different ways to use the router hook.

import { useRouter, usePrefetch } from 'next-router-provider';

const homeRoute = {
  pathname: '/'
}

const 4 = (id: string) => ({
  pathname: '/posts/[id]',
  query: {
    id
  }
})

function MyComponent() {
  const {
    pushRoute,
    replaceRoute,
    createLink,
    createHref,
    isRouteActive,
    createClickHandler,
  } = useRouter();

  // Get query parameter values. These can be typed but will default to Maybe<string>. This is useful when you know a query param will exist because it's part of the route.
  const searchFilter = useQuery('filter');

  // This will get the value of the route parameter. This will throw if the route param doesn't exist.
  const postId = useRouteParam('id');

  // Create a RouteLink. It has a lot of the same methods as the router but will point to a specific route.
  const homeLink = createLink(homeRoute);

  // Prefetch this bundle. If you have your own
  // Link component, you might put this in there and enable it using a prop.
  usePrefetch({
    pathname: '/'
  });

  // If there was a form submission...
  function onSubmit() {
    // ...save the form
    // Then navigate manually
    pushRoute({
      pathname: '/success'
    });
  }

  return (
    <nav>
      <a
        data-active={homeLink.isActive}
        href={homeLink.href}
        onClick={homeLink.onClick}
      >
        Home
      </a>
      {posts.map(post => {
        // If you wanted to do it manually
        const route = postsRoute(post.id);
        const href = createHref(route);
        const isActive = isRouteActive(route);
        const onClick = createClickHandler(route);

        return (
        <a
          data-active={isActive}
          href={href}
          onClick={onClick}
        >
          {post.title}
        </button>
      )
      })}
    </nav>
  );
}

Mocking

One of the benefits of using providers like this is that we can easily mock the router in tests.

If you don't want to run assertions on route changes, you can simply use the MockRouterProvider:

import { MockRouterProvider } from "next-router-provider";

render(
  <MockRouterProvider pathname="/posts/[id]" query={{ hello: "world" }}>
    <MyComponent />
  </MockRouterProvider>,
);

Now whenever any of the hooks are used within these components they'll be using a fake router.

If you want to assert that route changes are happening you can use the context directly and create your own router.

import { RouterContext, createMockRouter } from "next-router-provider";

describe("Submitting the form", () => {
  it("should redirect after submit", () => {
    const router = createMockRouter({
      pathname: "/posts/new",
    });

    jest.spyOn(router, "push");

    render(
      <RouterContext.Provider value={router}>
        <PostCreator />
      </RouterContext.Provider>,
    );

    const submitButton = await findByText("Create new post");

    act(() => {
      fireEvent.click(submitButton);
    });

    expect(router.push).toHaveBeenCalledWith({
      pathname: "/post/success",
    });
  });
});

API

Route

The majority of the methods use a Router object. This is similar to the Url object that the Next.js accepts.

interface Route {
  pathname: string;
  query: Record<string, any>;
}

The pathname can include parameters and they will be automatically parsed out.

pushRoute({
  pathname: "/posts/[id]",
  query: {
    id: "10",
    hello: "world",
  },
});

RouteLink

Returned by createLink. This can be used on anchors and buttons.

interface RouteLink {
  isActive: boolean;
  push: () => void;
  replace: () => void;
  onClick: ClickHandler;
  href: string;
}

useRouter(): RouterHook

Access the Next.js from the RouterProvider. Returns RouterHook.

RouterHook

  • getQuery<T>(key: string): T: Get a query parameter from the router
  • pushRoute(route: Route, options?: RouteOptions): void: Navigate to a Route.
  • router: The Next.js router
  • replaceRoute(route: Route, options?: RouteOptions): void
  • createLink(route: Route, options?: RouteOptions): RouteLink: Takes a route and returns a RouteLink.
  • createHref(route: Route): string: Takes a route object and returns a valid href.
  • createClickHandler(route: Route, options?: RouteOptions): void: Returns a click handler for a route that can be used on a link or anchor. This will respect clicks while holding modifier keys.
  • isRouteActive(pathname: string): boolean: Returns true if the route is active.

RouteOptions

Pass through options to router.push and router.replace. The only option is shallow: boolean.

pushRoute(
  {
    pathname: "/",
  },
  {
    shallow: true,
  },
);

useQuery<T>(key: string): T

Access a query string value and set it's type.

const id = useQuery<string>("id");

useRouteParam(key: string): string

This will get the value of the route parameter. Unlike accessing the query in the built-in router this doesn't distinguish between route params and query params. This hook will look at the current route to make sure the parameter even exists and will always return a string.

const postId = useRouteParam("id");

usePrefetch(route?: Route): void

Calls route.prefetch on the route.

usePrefetch({
  pathname: "/posts/[id]",
  query: {
    id: "10",
  },
});

Passing undefined or null will skip the effect.