Shallow route change hook

Jan 15, 2022

There are times in which you want to keep state for a react component for longer than the life of the component itself. Usually, you would use a state management tool like Redux or Zustand. But sometimes you might want to outlive the browser! đź‘».


Let’s think of two simple use cases:

  • You want to keep track of a modal’s state opened/closed.
  • You want to keep track of a search term on a page.

In both cases, you need to keep this state, even if the page is refreshed or the URL copied to a different window.

In such cases, even State Management Tools like Redux might fall short, given the fact that if you copy/paste the url to a different browser, that state wouldn’t be shared cross browsers.

So you end up with the good ol’ method of encoding that information in your URL! 🙌 A simple enough method – that must not be abused.

NextJS has a feature called Shallow Routing that allows you to modify the route without pushing this into the route page stack. Which would mean you keep the state, and you keep your page as is (no refresh ♻️)

The hook 🪝

Find below a hook template that helps you modify the state with a couple of simple lines.

Use the hook to add/remove attributes from your route based on actions in your component.


// my-custom-component.tsx
// Usage example of the hook below.
import { MyModal } from 'my-modal-component.tsx';

export const MyCustomComponent = (props) =>{
//...

const [setKeyModalOpen, unsetKeyModalOpen] =
    useShallowRouteChange("modalOpen");

const onOpen = () => setKeyModalOpen(true, onOpenModal);
const onClose = () => unsetKeyModalOpen(onCloseModal);

return (
	<div>
		<MyModal isOpen={isOpen} onClose={onClose}/>
	</div>
)
//...
}
my-custom-component.tsx


// use-shallow-route-change.ts
// Full hook code. Note the lodash dependency.

import React from "react";
import { omit } from "lodash";
import { useRouter } from "next/router";

export const useShallowRouteChange = (key) => {
  const router = useRouter();

  const setKey = (value, fn = (value: string) => {}) => {
    router.replace(
      {
        pathname: router.basePath,
        query: {
          ...router.query,
          [key]: value,
        },
      },
      null,
      { shallow: true },
    );
    fn(value);
  };
  const unsetKey = (fn = () => {}) => {
    router.replace(
      {
        pathname: router.basePath,
        query: {
          ...omit(router.query, key),
        },
      },
      null,
      { shallow: true },
    );
    fn();
  };

  return [setKey, unsetKey];
};
use-shallow-route-change.ts