Coder Social home page Coder Social logo

kyeotic / raviger Goto Github PK

View Code? Open in Web Editor NEW
133.0 4.0 18.0 7.36 MB

React routing with hooks

Home Page: https://kyeotic.github.io/raviger/

License: MIT License

HTML 0.50% JavaScript 2.81% CSS 0.28% TypeScript 96.36% Shell 0.05%
react routing react-hooks

raviger's Introduction

raviger

React Navigator. A React hook-based router that updates on all url changes. Heavily inspired by hookrouter.

Zero dependencies. Tiny footprint.

Note: Raviger is considered feature complete and will very likely receive only maintainace patches going forward.

Installation

npm i raviger

Docs

Complete documentation is available here on GitHub Pages

Quick Start

import { useRoutes, Link, useQueryParams } from 'raviger'

const routes = {
  '/': () => <Home />,
  '/about': () => <About />,
  '/users/:userId': ({ userId }) => <User id={userId} />
}

export default function App() {
  let route = useRoutes(routes)
  return (
    <div>
      <div>
        <Link href="/">Home</Link>
        <Link href="/about">About</Link>
        <Link href="/users/1">Tom</Link>
        <Link href="/users/2">Jane</Link>
      </div>
      {route}
    </div>
  )
}

Query Strings

import { useQueryParams } from 'raviger'

function UserList ({ users }) {
  const [{ startsWith }, setQuery] = useQueryParams()

  return (
    <div>
    <label>
      Filter by Name
      <input value={startsWith || ''} onChange={(e) => setQuery({ startsWith: e.target.value})} />
    </label>
    {users.filter(u => !startsWith || u.name.startsWith(startsWith).map(user => (
      <p key={user.name}>{user.name}</p>
    )))}
    </div>
  )
}

Navigation

The preferred method for navigation is the <Link> component, which uses all the same properties as the standard <a> element, and requires href. Internally <Link> uses history.pushState to ensure navigation without a page refresh. If you need to perform programmatic navigation raviger exports a navigate function.

Some routing libraries only trigger React component updates if navigation was triggered using specific methods, such as a specific instance of history. raviger listens for all popstate events and checks for changes. You can even have two isolated React instances on a page and URL changes will properly trigger raviger hooks.

raviger's People

Contributors

dependabot[bot] avatar fairyscript avatar hexid avatar kyeotic avatar macku avatar mentaljam avatar n33pm avatar roypearce avatar saghen avatar tschaub avatar zoontek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

raviger's Issues

Webstorm not Indexing Exports

Another hookrouter refugee here.

I'm using Webstorm as my preferred IDE. For some reason it's not able to find the exports from raviger, which causes annoyances such as:

  • having to manually import names instead of being able to rely on its auto import feature
  • not being able to see the names of the parameters for the functions / suggest what parameters should be passed.

useQueryParams hook returns stale values when going back in history

Hello, it's me again πŸ‘‹

So it seems that the latest changes introduced for useLocationChange have caused a problem with other hooks using it (not exactly sure why though).

When using useQueryParams, the URL param values returned when going back in history (web browser back button) do not reflect the current window location parameters, they are behind (stale).

I reverted to raviger 0.5.9 and the problem disappeared.

Would you have any idea what could cause that?
If that's too vague I can work on building a reproduction somewhere, let me know.

Allow to persist current history state when replacing state with navigate method

Hello there General Kyoetic πŸ––

Today, we ran into an issue with a self contained component that uses the History api to manage past states. That component pushes a new state with a state object when any of its values change (form type component) and also listens to the popstate event to then extract the state object and rehydrate the previous values in its React state. This is done to avoid unnecessary round trips to the API when we had already loaded these values previously.

All was fine with this component's life cycle when browsing through history with the Back and Forward buttons, however that stopped working when another unrelated component on the page started using raviger's navigate method with the replace boolean set to true.

The reason is that the navigate method always pushes a null state object (first parameter of history.replaceState).

window.history[`${replace ? 'replace' : 'push'}State`](null, null, url)

This causes our component to have lost the state object it had pushed, and thus it fails to rehydrate values properly.

We currently fixed it by replacing the navigate(url, true) call by this:

// reuse the current history state when replacing state
window.history.replaceState(history.state, null, url);
dispatchEvent(new PopStateEvent('popstate', null));

But ideally we'd like to have a way to do replace state with raviger's navigate while still reusing the current history.state.

Do you think there would be a way to introduce either an option, or a state object parameter to the navigate method?

Thanks a lot for your help πŸ˜ƒ

Possible race condition with `usePath`

Hello!

While working on a project @ my day job I came across what looks to be a race condition with usePath and navigate.

I've created a code sandbox that reliably replicates the issue:
https://codesandbox.io/s/ecstatic-silence-16zr4

Steps to reproduce

  1. Navigate to /about.
  2. Reload the page so that we 'start' on /about.
  3. Open up your browser console so you can see the logs.
  4. Press 1 on the keyboard.

What should happen: Navigates to home page, one console log from Home saying home path: /
What actually happens: Navigates to home page, but we get two console logs. One from the /about component, and one from the / root component.

From what I can gather, using navigate seems to update the value returned by usePath before the navigation is complete - thus giving components we are navigating away from the chance to run code based off the changed path value. For example, in a useEffect:

  useEffect(() => {
    setTabUrl(path);
  }, [path, setTabUrl]);

Using navigate() can cause some strange issues

Sandbox: https://codesandbox.io/s/keen-mahavira-jfxvk

In this example, I'm trying to return the user to the main page if somehow they arrive into my App on a sub-page in an unexpected state (no state in window.history).

To do this, on my sub-page, I check for the absence of window.history.state, and if its missing, I navigate back to /:

const { state } = window.history;

  if (!state) {
    navigate("/");
    return null;
  }

In my App, I log which path we are currently on:

console.log(usePath());

When the App is first loaded, we see / as the path, and if we navigate to the error page, we correctly see /error as the path.

But if we now reload the App while we are on the /error path, we correctly see 1 log for /error, and then we see navigating to /, and now incorrectly the path appears to be /error again, but the App renders the Main component. And now the app is stuck and further navigation is impossible.

Screen.record.from.2022-02-03.11.03.35.mp4

useNavigationPrompt does not work with Back button

Thanks for the work & maintenance you put into this router. I have found an issue where I have a complex form, and need to prompt the user if they try to navigate away with unsaved changes. That works great for events that trigger the beforeunload event (hard page reload, navigate to a different site outside the SPA) or that use raviger's navigate, but it doesn't work with the browsers Back button.

Has there been any consideration for supporting the interception of the back button within the SPA?

Have `navigate` respect the basePath like `Link` does

Thanks for writing this library. I started with hookrouter and hit Paratron/hookrouter#70 and saw your link to this repo.

I was wondering if it was possible (either by default behaviour or via a flag) to have navigate respect the basePath it is inside the same way that Link does, so I can use paths relative to the basePath rather than the server root?

So navigate('/bar') would end up going to /foo/bar when used inside a route with a base path of /foo

Thanks

basePath issues

Hello! πŸ‘‹

First, thanks for this fantastic library, I'm using it on 6 projects and everything went well!
Except some small issues with basePath. I'm using some workarounds, but I though it was not a good idea to keep the infos.

So I made a small sandbox to illustrate them:
https://codesandbox.io/s/late-waterfall-qbl1f?file=/src/App.tsx

First, you can see that even if the basePath is set to /foo, both /page and /foo/page will match the route. I think only the second is correct here.

Secondly if you update the basePath dynamically, the matching will not be performed again.

  1. Go to /foo/page
  2. Switch the basePath to /bar using the dropdown
  3. The matching is not re-performed: we don't see the 404

Base path matcher regexp is not case insensitive

Hi @kyeotic πŸ‘‹

We had a bug ticket in production where our silly users decided to access our single page app by changing some part of the base path URL to be lowercase (silly I know).

Unfortunately that is something that we used to support before so we had to write a temporary fix on our side.

The issue is here:

raviger/src/path.js

Lines 78 to 80 in ea4a3f5

function basePathMatcher(basePath) {
return new RegExp('^' + basePath)
}

If the line was changed to make the RegExp case insensitive, our problem would be solved:

return new RegExp('^' + basePath, 'i')

Would it be possible to either make it case insensitive, or have an option to do so?

Thanks in advance!

Route to modal

Hello,

Thank you very much Kyeotic for bringing back and supporting hookrouter.

I'm currently trying to open a modal for a route without changing the current content of the page. Is it even possible with Raviger to do that or I should switch to another router ?

I've made an incomplete codesandbox since I'm quite stuck:

https://codesandbox.io/s/distracted-snowflake-6cqcb?fontsize=14&hidenavigation=1&theme=dark

What would you do ? How ?

The final goal is to have:

  • A login button that opens the modal
    • (If the user uses the direct url, it should open the Modal on the "/" root page)
  • Have a register modal and a forgot password modal too.

Thank you so much for your suggestions and help

Regards

Problems with navigation and nested routes

Hi, I've found some strange problems with navigation when you are using nested routes with params. I'm testing with the latest version 1.4.

How to reproduce?

Here is the link to the reproduction code:
https://stackblitz.com/edit/react-raviger-test?file=index.js

Press on the links in the bottom part in that order to:

  1. nested with a param About (the route is loaded)
  2. nested with a param Test (the route is not loaded)
  3. root (the route is loaded)
  4. nested with a param Test (the route is loaded)
  5. root (the route is loaded)
  6. nested with a param Test (the route is loaded)
  7. nested with a param About (the route is not loaded)

raviger-bug

Possible cause

I've checked many things, and I think the useLocationChange hook is the main suspect. It looks like the if condition with inheritBasePath option is causing the problem. I haven't found any other place where this option is used internally.

I was trying to debug the code with breakpoints in browser devtools. After setting the value of basePath to what is provided with options the issue is gone:
inheritBasePath-hack
raviger-fixed

Do you reckon we could somehow fix this behaviour? Maybe we just need to remove the inheritBasePath condition πŸ˜„ or force the false value when using hook internally from the useRoutes hook?

Navigate + queryParams and useRedirect

First and foremost, thanks for this. I just started playing around with raviger and I really like it so far!

I've a couple of questions.

For context; we have a react application with a few pathnames and a bunch of parameters as query strings. Some of them are optional, other obligatory. Some get hydrated as entities, others are just UI settings.

Most importantly we share some of them between pages, which means that we adjust them when a user switches from a route to another. Eg: some are preserved, other are removed or altered.

Questions:

  1. Do you plan on supporting passing an object with queryParams to the navigate as hookerouter does? At the moment I use navigate in combination with useParams and do something like:
navigate(path + '?' + (URLSearchParams(queryParams).toString()))

Which feels a bit odd πŸ˜•

  1. Any plans on adding a (use)Redirect functionality like hookrouter has?
    If not, do you plan to expose the router context so we may extend on that and build it ourselves?
    If that's also not an option, what do you think would be the best way to handle redirects with raviger?

Thanks!

It's support lazy-load?

I want to use lazy-load with parameterized route

const routes = {
  '/': () => <Home />,
  '/about': () => ??? (lazy-load),
  '/users/:userId': ({ userId }) => ??? (lazy-load)
}

Unnecessary re-renders using useQueryParams

Hey again @kyeotic πŸ‘‹

I am currently investigating some performance issues we are experiencing when changing URL parameters with useQueryParams.

I am finding issues within our own code but also what I think could be issues in raviger.

For instance, I have found this and I am not sure if it's an issue or a feature request: Code Sandebox

Whenever setting the same values for the parameters, the RouterContext.Provider value changes, causing the components consuming the context to re-render.

The problem is here: router.js#L32

Each render loop generates a new object reference passed to the context provider, the value is not memoized with useMemo, and there is nothing that checks for deep equality of the parameters to prevent passing a new object reference either.

Additionally, pushing the same query params still pushes a new history state, and I am not sure if that is expected or a bug?

Bug when using a percent (%) symbol query parameter value

When a percent (%) symbol is used as part of the query parameter it will blow up on decodeURIComponent because URLSearchParams returns the value already decoded.
This was tested on:

  • Chrome 68.0.3440.84 on Mac
  • Microsoft Edge 80.0.361.33 on Mac
  • Safari 13.0.2 (15608.2.30.1.1)

The current workaround is to encode the URI before sending to setQueryParams

<Link> component does not respect target attribute

Hi there, thanks for the great library!

I am not sure if it's a bug or if working as designed, I'll let you decide that, but the documentation states:

The preferred method for navigation is the <Link> component, which uses all the same properties as the standard <a> element, and requires href.

...while in practice, when setting a target prop on the <Link> component it does not behave as expected (not as an <a> tag would).

E.g.: <Link href="/dashboard" target="_blank" /> for which you would expect the link to open in a new tab.

You could argue that there is no reason to use a <Link> component for this use case and just revert to using a simple <a> instead, which would be fair.

useLocationChange - check if the path exists

Hi,
When using useLocationChange hook, I would like to check whether the path exists or not.
My goal is to create a 404 page and navigate to it programmatically (using "navigate" function) when the path doesn't exist.
I have read the documentation, but I didn't find any clue for that.

Scroll restoration

I currently use this extended version of useRoutes:

type ScrollRouteOptionParams = RouteOptionParams & {
  scrollRef: RefObject<HTMLElement> | 'body';
};

const useRoutesWithScroll = <T extends string>(
  routes: Routes<T>,
  { scrollRef, ...options }: ScrollRouteOptionParams
): JSX.Element => {
  const result = useRoutes(routes, options);

  const scrollToTop = useCallback(() => {
    if (scrollRef === 'body') {
      window.scrollTo(0, 0);
    } else {
      scrollRef.current.scrollTo(0, 0);
    }
  }, [scrollRef]);

  useEffect(scrollToTop, [result, scrollToTop]);

  return result;
};

It kinda works, but the true implementation would be able to restore scroll positions. I think it should be easy enough, but since you emit a popstate event when navigating (https://github.com/kyeotic/raviger/blob/main/src/navigate.ts#L73), it's hard to tell the difference between naviagtion and back-navigation. Could you use a different event or maybe tag the event somehow, so it's easy to filter out?

chroma code fences don't work

This is an issue with the Hugo docs.

For some reason chroma isn't properly highlighting code fences blocks (code in triple-backticks) the same way it highlights code in highlight-shortcodes.

You can see this in the chromaFences branch. I don't know the cause, but I would prefer to be able to use code fences since its natural markdown.

Any help understanding this issue would be appreciated.

Ship sourcemaps in package

Hi!

I've run into a couple of cases now where Raviger has been throwing an error and I can't view the source to debug :(

I can see in your Rollup config you are building sourcemaps, so I'm wondering if you can ship those in the bundle somehow so that I can properly debug during development?

Thanks!

Redirect does not render component that matches route

Hey Tim, I'm starting up a new repo and encountering this issue that I've reproduced in this codesandbox https://codesandbox.io/s/stoic-cori-xol1o?file=/src/App.tsx

Basically I want to use Redirect to match any non-matching routes on page load and redirect to a specific route that has a component. What I'm seeing though is that if there are any paths that match the Redirect, the location in the url updates as expected, but the component that matches that route does not render.

Hopefully I'm missing something, but I can't find anything that might help. Thanks.

useRedirect hook

It would be nice to be able to setup redirects via a useRedirect hook or similar mechanism.

I expect the router to navigate the user to the target path (preserving the queryString if present) and replacing the navigation history with the destination path.

Basic example

import {useRoutes, useRedirect} from 'raviger';

const routes = {
  '/dashboard': () => 'Dashboard'
};

const MyApp = () => {
  useRedirect('/', '/dashboard);
  return useRoutes(routes) || 'oops';
}

I'd expect the user to end up in the /dashboard page when hitting the root of the App and having only /dashboard in its History.

Extra
Could be interesting to think about merging queryParameters defined in the useRedirect hook configuration with the ones provided in a Link.
For example I could setup a redirect such as

  useRedirect('/', '/dashboard?redirected=1');

Or

  useRedirect('/', '/dashboard', {redirected: 1});

And then later in a hypothetical SomePage component make a Link to go back to the Dashboard

  <Link href='/?from='some-page''>Go back</Link>

In this case I'd expect the queryParameters to contain both redirected and from

But this is already quite advanced, I'd be very happy already with the Basic functionality :)

Is there anyway to handle an optional trailing`/` in route path without using `*`?

I have this routes definition

const routes = {
    '/foo': () => <Foo />,
    '/bar': () => <Bar />,
    '/qux': () => <Qux />,
    '/qux/:quxId': ({ quxId }) => <Bar quxId={quxId}/>,
};

As you can see there is a special case when 'qux/:quxId' routes to a different view than '/qux' (In fact it routes to the view for '/bar').

I want to be able to handle any of the routes also being visited with a trailing / (for example '/foo/').

I can get it to work with * but it has a couple of downsides.

  1. Because * can match anything, not just the trailing /, routes are able to be activated with an invalid path (/foot for instance)
  2. It depends on ordering '/qux/:quxId' before '/qux' so that the * doesn't clobber the more specific path. This feels pretty fragile, especially because routes is an object, not an array.

The other alternative I can see is to define 2 paths for each route, one with the slash and one without.

const routes = {
    '/foo': () => <Foo />,
    '/foo/': () => <Foo />,
    '/bar': () => <Bar />,
    '/bar/': () => <Bar />,
    '/qux': () => <Qux />,
    '/qux/': () => <Qux />,
    '/qux/:quxId': ({ quxId }) => <Bar quxId={quxId}/>,
};

Obviously that is not very appealing

Is there any other way to address this special case of optionally allowing a trailing slash, or is it something you would consider adding?

Thanks as always πŸ‘

useHistoryState

From this comment

state is accepted by navigate and set on history, but it is not accessible via the raviger API. We need a hook to expose it

function useHistoryState() {
  const { state } = window.history;

  return state;
}

Could we have the location.search value returned next to path in useLocationChange?

I'm doing some magic with this side effect to handle a referral value in the URL

And I need to compare the window.location.search value at the moment of the side effect callback being called, so it would be nice if we could get it next to path, maybe something like

useLocationChange(location, { ... } )
// and then we can just deconstruct what we need

useLocationChange({ pathname, search }, {...})

This way we don't need to use window.location.search if we need it's value and risk closure issues or someone moving this value out of the side effect thinking they can reuse a cached const

What do you think?

usePopState has wrong type definition, hook dependencies

Hey again!

So I have a use-case where a modal dialog contains links and the modal needs to close on any history change.

Rather than making my own listener, I went to see how raviger implements it, and found the nifty usePopState hook that does pretty much what I need. However I found the following:

  1. usePopState is exposed but has no entry in the documentation. Is it part of the API? Is it fine to use it?

  2. In the type definition, it says that the predicate parameter is a function that returns a boolean, however, according to the actual implementation and usages across the library, it seems that it's only expecting a boolean.

  3. The predicate parameter is not added to the dependencies of the useEffect hook, which means that if we need to change that condition after the initial render, it won't be taken into account.

  4. I feel that predicate should be an optional argument, and could be moved to being the third parameter?

  5. Probably have the same feeling about the first parameter. Seems like basePath could be optional and default to ''.

Looking forward to hearing about your opinion on these πŸ‘

Compatibility with Typescript 3.x

When I upgraded from v2.4.2 to v4.0.0 of raviger, I was greeted with a wall of errors.

[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(16,18)
      TS1110: Type expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
16:27-31
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(16,28)
      TS1005: '}' expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
16:31-32
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(16,32)
      TS1128: Declaration or statement expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
20:70-71
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(20,71)
      TS1005: ';' expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
20:71-72
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(20,72)
      TS1005: ';' expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
20:78-80
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(20,79)
      TS1005: ';' expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
20:81-82
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(20,82)
      TS1109: Expression expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
23:148-149
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(23,149)
      TS1005: ';' expected.

ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts
23:149-150
[tsl] ERROR in /Users/roy/code/my-repo/node_modules/raviger/dist/router.d.ts(23,150)
      TS1005: ';' expected.

After looking into ts-loader (webpack), my tsconfig.json and other sources that could cause this issue without success, I noticed that raviger was using Typescript 4.x while my project was using Typescript 3.x.

Upgrading to Typescript 4.x resolved these issues.

1.0 release

Now that navigation guards and a documentation site are in place I believe raviger is ready for a 1.0 release. I plan on leaving this issue open until Tuesday Nov 12th to give anyone an opportunity to raise any concerns with the current API before it is locked down.

If nothing blocking is raised I will cut a release candidate on Tuesday Nov 5th, and then release 1.0.0 on the 12th.

useNavigate does not allow me to set a State

If I use the navigate() function from https://kyeotic.github.io/raviger/navigate/, I can pass state as the 4th parameter and set some state. However if I call useNavigate() from https://kyeotic.github.io/raviger/use-navigate/, the function it returns does not allow state as the 4th parameter, but the docs say:

The function returned by useNavigate has the same signature as the non-hook navigate function.

Are the docs wrong or is the implementation incorrect?

Additionally, in the types that are generated for the project, the navigate() function has the following definitions:

export declare function navigate(url: string): void;
export declare function navigate(url: string, replace: boolean): void;
export declare function navigate(url: string, query: QueryParam | URLSearchParams): void;
export declare function navigate(url: string, query: QueryParam | URLSearchParams, replace: boolean): void;
export declare function navigate(url: string, queryOrReplace?: QueryParam | URLSearchParams | boolean | null, replace?: boolean): void;
export declare function navigate(url: string, query: QueryParam | URLSearchParams, replace: boolean, state: unknown): void;

this means there is no valid way to pass a null for the query and replace parameters when you try to also pass a state parameter.

Early issue with nested `useRoutes`

Sorry for the vague issue but I just thought I would give you a heads up while investigating.
Since upgrading to 1.0.0-rc.3 (Also tried rc6) we have been having issues with previously working nested useRoutes.

The basic structure of our app is as follow

An "App" with useRoutes that either maps to a list page or a view entity page
basePath is foo/bar/entities
Routes are similar to the following

const routes = {
    '/': () => <ListPage />,
    '/:id/*': ({ id }) => (
        <ViewPage id={id} />
    ),
}

A view entity page with useRoutes that maps to 3 tabs within the page,
basePath is foo/bar/entities/<id> where id is a numeric id.
Routes are similar to the following

const routes = {
    "/tab1": () => <Component1 />,
    "/tab2": () => <Component2 />,
    "/tab3": () => <Component3 />
}

Previously (0.5.8) we were using usePath within one of the tabs (so 2 useRoutes deep) with no problems, it would always return the path we expected (/tab2)
Now however, if you land on tab2 (i.e. start a new browser tab at the url foo/bar/entities/1/tab2) usePath will return the correct value (/tab2)
But as soon as you navigate to a different tab usePath (called within the tab) starts returning /1/<tabNo>. The basepath in the context is correct (has the id at the end of the path), but it's like it calculated the path based on the basePath of the router one level higher.

context = { path: '/1/tab2', basePath: 'foo/bar/entities/1' }

I'll keep adding detail to this issue as I investigate, but I thought it might be possible you have an idea what's going on.

Thanks

route change interceptors

We need a way to confirm route changes before they happen. We need interceptors. I believe this may be the last feature before going to 1.0.

I am currently working on an API, but my home development time has been severely restricted the last two weeks due to a neck injury. Any ideas or sub-feature requests for interceptors are welcome here.

Confirmation

In the confirmation case it is expected that the the intercept-handler will gather user input and decide whether to cancel or allow the navigation to occur.

This could be achieved either through immediate cancellation that is resumed if the intercept-handler indicates, or through synchronous/browser-managed confirmation. However, according to the MDN

To combat unwanted pop-ups, some browsers don't display prompts created in beforeunload event handlers unless the page has been interacted with. Moreover, some don't display them at all.

I believe using the browser built-ins is the right way to go, even if some browsers don't support it (that is an intentional decision we would be fighting).

Inspection

In the inspection case it is expected a component may want to conditionally alter navigation changes or block navigation. Blocking navigation without a prompt may not be possible in all situations, but should be possible if navigation was triggered from within raviger.

API Proposals

Simple

Provide a confirmation prompt that will be presented to the user if they try to navigate. Only active if predicate is true.

function useInterceptor(prompt: string, predicate = true: boolean) : void

Functional Sync

Provide a function that will receive the current route and a function that will cancel the transition. Must handle syncronously.

function useInterceptor(interceptFn: (route: string, cancelFn: () => void) => void) : void

Functional Async

Provide a function that will receive the current route and returns the intended route as a promise. can return the same route to allow, or null to cancel.

function useInterceptor(interceptFn: (route: string) => Promise<string | null>) : void

useLocationChange is called on mount

Hi @kyeotic!

We have just upgraded raviger from version 1.6.0 to version 2.0.2 and we are facing a problem with a new behaviour introduced in 2.0.0 in useLocationChange.

useLocationChange will now call its callback argument on mount due to this useLayoutEffect:

raviger/src/path.js

Lines 93 to 98 in 74e3b87

// When the basePath changes re-check the path after the render completes
// This allows nested contexts to get an up-to-date formatted path
useLayoutEffect(() => {
if (isActive !== undefined && !isPredicateActive(isActive)) return
setRef.current(getFormattedPath(basePath))
}, [basePath, isActive])

Is that completely intended? Or should that be a layout effect that only gets executed after the initial mount?

Our use case for useLocationChange is within modals that we want to close when the user hits the Back/Forward browser history buttons:

// close the modal when the path has changed
useLocationChange(onClose);

With 2.0.2, this code gets executed when the modal is mounted and thus it closes instantly.

Allow passing ref to the Link anchor.

I'm currently using the raviger Link for a list of links and I'm trying to use keyboard navigation to navigate through them.
To move the focus through the links I would need to set a ref on the anchor elements, this is currently not possible.
This would be easily achieved by adding forwardRef on the Link component and then pass the ref to the anchor element.
I would really appreciate if this could be implemented. :)

Class on active links

If the Link component has the path /login and the user is currently navigated to /login, the Link component could have a class labelling it as an active link as used in the vue-router: https://router.vuejs.org/api/#linkactiveclass

The API could follow after react-router: https://reacttraining.com/react-router/web/api/NavLink

Additionally, a class for exact and non-exact matching would be appreciated. For example, if the user is at /user/login the /user Link could have an link-active class while the /user/login Link could have an link-active-exact class.

basePath not working on top level component

Hello,

First - thanks for this nice library. I have s one struggle. In hookrouter you can use setPath function on start application (https://github.com/Paratron/hookrouter/blob/master/src-docs/pages/en/05_serverside-rendering.md#setting-the-path)

I have react application which is serving in subofolder - for example (www.sampleapp.com/subfolder/sampleapp.

In top level component i have define routes:

const route = useRoutes(AppRoutes, { basePath: "sampleapp" });

But this is not working.

Is there any workaround, how i use basePath(setPath) for this usecase?

Thanks

Links that ignore basePath?

Thank you for creating Raviger, it is really close to the routing lib I was considering making out of frustrations with the existing ones! I really like the "cascading routes" concept that allows me to extract, say, a tab-bar and its contents into a component and have it not care about which base route it sits on.

That said, there are times where I want to ignore the basePath and link to something outside the scope of the closest useRoutes context. I am currently solving that with relative links that go through parents (i.e. ../../account), but that is now another way of coupling the specific path to my component. I tried overriding basePath with '/', '' and null which doesn't work (strips away the hostname and everything, or does nothing). Am I missing something fundamental, or should we consider adding an isAbsolute or similar property to Link?

EDIT: I should add that I am happy to make a PR for this if you agree.

useRedirect add `?null=` to final url when queryParams is not provided

Hi,

I found a small issue with useRedirect when no query parameters are provided.

Suppose we have a redirect setup as

  raviger.useRedirect('/', '/welcome');

It will actually end up in /welcome?null=

Why

Since useRedirect and navigate are defined as:

function useRedirect(predicateUrl, targetUrl, queryParams = null, replace = true) {
    ...
    navigate(targetUrl, queryParams, replace)
    ...
}

...

function navigate(url, replaceOrQuery = false, replace = false) {
  if (typeof replaceOrQuery === 'object') {
    url += '?' + new URLSearchParams(replaceOrQuery).toString();
  } else {
    ...
  }
...

Due to the fact that queryParams defaults to null and typeof null === 'object' we end up with this line running with replaceOrQuery set to null.

  url += '?' + new URLSearchParams(replaceOrQuery).toString();

And for some reason

new URLSearchParams(null).toString();

Results in "null="

I think this can be fixed simply by checking for null replaceOrQuery in navigate:

if (replaceOrQuery !== null && typeof replaceOrQuery === 'object') {

Cannot remove URL parameters using useQueryParams

Hey again,

This one is probably a feature request, but currently it is not possible to remove a URL parameter using the useQueryParams setter function.

For instance doing setQueryParams({ foo: null }); sets the value of foo to the string "null".
I would have expected undefined or null to actually remove the parameter from the URL.

Otherwise, the only current solution is to use replace: true, which forces the component to retrieve and potentially alter the parameters it should not know about.

Here is an example: Code Sandbox

node <15.0.0

Hello! πŸ‘‹

is there any reason why node 15 is not supported?

cheers.patrick

Route matching hook

It would be nice if a component could get matching state for an individual route, or a set of routes. Unlike useRoutes this hook wouldn't be to render content, just to identify an active state for to apply "route-has-matched" behavior or styling to a component.

This came from the discussion in #105

Use Cases

  • A <nav> section, like a tab, that wanted active styling for a route. Like <ActiveLink >, but more general.
  • A toolbar that might hide/display when a route or querystring-value was present

I can't think of any more off the top of my head, so use cases are welcome.

Considerations

  • for a single route returning a boolean seems sufficient
  • for a set of routes returning a string that matches the route could be awkward if there is a way to match on querystring-values
  • this can already be achieved manually by combining usePath with useQueryParams. Is it worth introducing a new hook to serve this case?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.