0.2.0 • Published 8 months ago

react-nav-handler v0.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

React nav handler

Decouple navigation requests from url changes.

Rationale

Components may sometimes request to change the url. For example, a HelpButton component may want to navigate to the help screen when pressed. If you want to reuse such a component in different contexts, then the required url change may depend on the context. For example, in the context of the "editor" page the help button should navigate to "/editor/help", but in the context of the "dashboard" page the button should navigate to "/dashboard/help".

This problem can be solved by informing the HelpButton about its context, or by explicitly passing a url into the HelpButton. Although this simple solution is usually all you need, there are cases where it leads to a lot of prop drilling, especially when components are reused in different locations. In those cases, the code will be simpler and cleaner if components can just call navToHelp and remain agnostic of the exact url change that is required.

We can achieve this by adding so-called navigation pages to the url-router. When a navigation page is mounted, it registers itself and its associated url in the navigation handler. In the example code below, the EditorNavPage is associated with /editor and the DashboardNavPage is associated with /dashboard. When a component calls navToHelp then this request is passed (under the hood) to the navigation handler, who will call the navToHelp function of the navigation page that matches the current url. This means that depending on the current url, the HelpButton will navigate to /dashboard/help or to /editor/help.

Synopsis

// navEvents.ts
export const navToHelp = (): void => {
  navHandler.getNavFn('navToHelp', navToHelp)();
}

// DashboardNavPage.tsx
import { useInstallNavPage } from 'react-nav-handler';
import { navToHelp } from 'navEvents';

export const DashboardNavPage = (props: { children }) => {
  useInstallNavPage(
    'DashboardNavPage',
    {
      navToHelp: (() => {
        history.push('/dashboard/help');
      }) as typeof navToHelp,
    }
  );
  return <>{props.children}</>;
};

// EditorNavPage.tsx
import { useInstallNavPage } from 'react-nav-handler';
import { navToHelp } from 'navEvents';

export const EditorNavPage = (props: { children }) => {
  useInstallNavPage(
    'EditorNavPage',
    {
      navToHelp: (() => {
        history.push('/editor/help');
      }) as typeof navToHelp,
    }
  );
  return <>{props.children}</>;
};

// UrlRouter.tsx
import { DashboardNavPage } from 'DashboardNavPage';
import { EditorNavPage } from 'EditorNavPage';

export const UrlRouter = () => {
  return (
    <>
      <DashboardNavPage>
        <Route path="/dashboard">
          <HelpButton />
        </Route>
      </DashboardNavPage>
      <EditorNavPage>
        <Route path="/editor">
          <HelpButton />
        </Route>
      </EditorNavPage>
    </>
  )
}

// HelpButton.tsx
import { navToHelp } from 'navEvents';

export const HelpButton = () => {
  return (
    <button onClick={() => navToHelp()}>Help</button>;
  )
}

More fine-grained control

The proposed approach to navigation is unusual, because navigation happens based on the current url, but not on the caller. Specifically, if the caller is a React component, then it doesn't matter in which part of the rendering tree that component is located. This can be too coarse grained though. For example, imagine that we're on the url /editor and we're showing an Editor component that has a HelpButton. Following the previous example, when we press this button, we will navigate to /editor/help. However, the page may also have a HelpButton in the application frame that should always navigate to /help. To support such cases, you can add a requesterId in the call to navToHelp.

// navEvents.ts
export const navToHelp = (requesterId: string): void => {
  navHandler.getNavFn('navToHelp', navToHelp)(requesterId);
};

// EditorNavPage.tsx
export const EditorNavPage = (props: { children }) => {
  useInstallNavPage('EditorNavPage', {
    navToHelp: ((requesterId: string) => {
      if (requesterId !== 'Editor') return false;
      history.push('/editor/help');
    }) as typeof navToHelp,
  });
  return <>{props.children}</>;
};

What this communicates is that EditorNavPage will handle navToHelp requests from "Editor", but not from other requesters. When a navivation function returns false then navHandler will continue by passing the navigation request to the next navigation page. Of course, you must ensure that one of the mounted navigation pages processes the requests, or an error will be thrown.

0.2.0

8 months ago

0.1.0

8 months ago