react-router icon indicating copy to clipboard operation
react-router copied to clipboard

[v6][Bug]: HashRouter and wildcard route "*" does not match when provided with URL with characters between "#" and "/" eg. "#unknown/"

Open hrstkatomas opened this issue 3 years ago • 2 comments

What version of React Router are you using?

6.2.1

Steps to Reproduce

<HashRouter>
	<Routes>
		<Route path="world" element={"Hello World"} />
		<Route path="*" element={"World not found"} />
	</Routes>
</HashRouter>

And navigate to hash #unknown/world

Expected Behavior

I expect World not found to be rendered. I expect wildcard route to match every unknown path so I can notify user about the problem.

Actual Behavior

Wildcard route <Route path="*" does not match. Shows blank page.

This issue is possibly related to issues #8428 and #8517, but neither mentions HashRouter explicitly. The closest described issue is @vreezy's https://github.com/remix-run/react-router/issues/8428#issuecomment-985402731

hrstkatomas avatar Feb 02 '22 10:02 hrstkatomas

Btw my workaround:

   useEffect(() => {
      if(window.location.hash.indexOf("#%") > -1 ) { 
         window.location.hash = ""; 
      }
   });

vreezy avatar Feb 02 '22 11:02 vreezy

In HashRouter, I face similar problems in the following structure

<HashRouter>
        <Routes>
          <Route path="/" element={<Layout/>}>
            <Route index element={<HomePage/>} />
            <Route path='xxx' element={<XXX/>} />
            <Route path='*' element={<ErrorPage/>} />
          </Route>
        </Routes>
</HashRouter>

In the above structure, it works as follows:

✔matched by path='*', work fine

'https://localhost:8000/#/unknown'
'https://localhost:8000/#/unknown/123'

---
❓expected to be matched by path='*';
but actually is matched by outer path='/' and shows index element

'https://localhost:8000/unknown'
'https://localhost:8000/@'
'https://localhost:8000/@unknown'

---
❌failed, shows blank pages,
throws 'No routes matched location "unknown" ' warns.

'https://localhost:8000/#unknown'
'https://localhost:8000/#unknown/123'

(a server error will occur for URLs like 'https://localhost:8000/@/unknown', 'https://localhost:8000/unknown/123',)

expected behavior

path='*' should match following

'https://localhost:8000/#unknown'
'https://localhost:8000/#unknown/123'
'https://localhost:8000/unknown'
'https://localhost:8000/@'
'https://localhost:8000/@unknown'

index element should only be shown when the URL is

'https://localhost:8000/'

Ellie-Yen avatar Apr 06 '22 07:04 Ellie-Yen

This issue has been automatically marked stale because we haven't received a response from the original author in a while 🙈. This automation helps keep the issue tracker clean from issues that are not actionable. Please reach out if you have more information for us or you think this issue shouldn't be closed! 🙂 If you don't do so within 7 days, this issue will be automatically closed.

github-actions[bot] avatar Apr 25 '23 00:04 github-actions[bot]

#unknown/world is not valid here since hash router expects the hash to be a valid path starting with a slash (as a normal non-hash path would). #/unknown/world works as expected.

brophdawg11 avatar Apr 28 '23 20:04 brophdawg11

@brophdawg11 great, but what if for whatever reason the pages goes to /#abc? It can't be handled and we can't even redirect the user to 404. It certainly should not be marked as closed and resolved.

filthyrichmoneky avatar Jul 14 '23 11:07 filthyrichmoneky

Can you elaborate on the use-case in which your app is ending up at a malformed hash path like #abc instead of #/abc?

brophdawg11 avatar Jul 17 '23 16:07 brophdawg11

There is for example a 3rd library, which upon some interaction returns back to our app (website) with a path like .com/#8ca38640 (I'm assuming they're using that data somehow). This makes however the page go blank, which confuses users.

filthyrichmoneky avatar Jul 17 '23 19:07 filthyrichmoneky

Can you elaborate on the use-case in which your app is ending up at a malformed hash path like #abc instead of #/abc?

Sharepoint online. I used the Router in a spfx. If you set a page in edit mode it add some not working routes to the url. Sharepoint didnt use a router. It used it for other behaviors

vreezy avatar Jul 17 '23 19:07 vreezy

It feels fragile to be using a HashRouter if you have other third party libraries also setting hashes (let alone setting them to invalid values w.r.t React Router)? That would be somewhat like using a BrowserRouter and expecting third parties to be able to just window.history.pushState and have the router behave correctly?

returns back to our app

Is this always a fresh page reload? Would it be possible to detect this hash and reload the window without it prior to instantiating React Router?

If React Router did detect/correct the hash to add the leading slash, would your app then just show a 404 page?

brophdawg11 avatar Jul 18 '23 20:07 brophdawg11

It feels fragile to be using a HashRouter if you have other third party libraries also setting hashes (let alone setting them to invalid values w.r.t React Router)? That would be somewhat like using a BrowserRouter and expecting third parties to be able to just window.history.pushState and have the router behave correctly?

returns back to our app

Is this always a fresh page reload? Would it be possible to detect this hash and reload the window without it prior to instantiating React Router?

If React Router did detect/correct the hash to add the leading slash, would your app then just show a 404 page?

I don't know why we complain now for third parties. Even a User can add a wrong hash. Docu says it should route than to * and this dosen't happen.

I see only 2 options

  1. Change Code
  2. Change docu

vreezy avatar Jul 18 '23 20:07 vreezy

Can you elaborate on the use-case in which your app is ending up at a malformed hash path like #abc instead of #/abc?

Example: There is a user's page with HTML anchors for scrolling to the bottom form #bottomForm. And he wants to save this functionality. Our application is installed as embed code on their page, that's why we use HashRouter. So he creates a link with the anchor to the bottom page to have the possibility to navigate to the form from email. but in that case, we can't open our main page in the embed application

ruslanvyaznikov avatar Jul 19 '23 07:07 ruslanvyaznikov

@vreezy Can you point me to where the docs state that a wrong hash should route to *? Maybe we just need to get that updated.

@ruslanvyaznikov Is #bottomForm an element on their HTML page? Or is that a route your <HashRouter> app is supposed to match and render?

brophdawg11 avatar Jul 19 '23 11:07 brophdawg11

@vreezy Can you point me to where the docs state that a wrong hash should route to *? Maybe we just need to get that updated.

@ruslanvyaznikov Is #bottomForm an element on their HTML page? Or is that a route your <HashRouter> app is supposed to match and render?

First, i have a quetion. Whate is a "wrong hash" ? your docu don't declare this meaning.

But at no match route stands: that any "non matching" route redirect to this element. In my thinking is what you call a "wrong hash" is a "non matching" route. But you say a "wrong hash" is not covered by "non matching".

So docu need a declaration of wrong hashes and a update that non matching works not for wrong hashes.

vreezy avatar Jul 19 '23 12:07 vreezy

Btw my workaround:

   useEffect(() => {
      if(window.location.hash.indexOf("#%") > -1 ) { 
         window.location.hash = ""; 
      }
   });

I've come across this issue also. I tried your work-around approach, but with no luck. So I've come up with my own solution (for anyone who might find this thread useful):

const InvalidHashRouteCatcher = ({ regex, children }: InvalidHashRouteCatcherProps) => {
    const navigate = useNavigate();
    useEffect(() => {
        const match = window?.location?.hash.match(regex);
        if (match && match.length) {
            navigate(`/${match[0].replace("#", "")}`);
        }
    });

    return <>{children}</>;
};

So you can do this in you code:

<HashRouter>
    <InvalidHashRouteCatcher regex={/#\w+/gi}>
        <Routes>
                              ...
        </Routes>
    </InvalidHashRouteCatcher>
</HashRouter>

But I think this should be fixed in the Router itself. Or at least being able to catch the "No route matches..." warning.

kluverp avatar Jul 26 '23 08:07 kluverp

I chatted with @mjackson and we're going to re-open this to account for the inconsistency between browser routers where anything unmatched will go to a root * route and hash routers where leaving off the leading slash causes it to not match a root * route. This is because with window.location.pathname the leading slash is guaranteed - but our hash history implementation does not behave the same way. Thanks for discussing these uses cases with us!

brophdawg11 avatar Aug 01 '23 16:08 brophdawg11

This is resolved by #10753 and will be available in the next release

brophdawg11 avatar Aug 01 '23 16:08 brophdawg11

We just published version 6.15.0-pre.0 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

brophdawg11 avatar Aug 09 '23 17:08 brophdawg11

We just published version 6.15.0-pre.0 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

Hi, sorry for the late reply (I was on vacation). Just tested it, and ik seems to work fine! Thanks!

kluverp avatar Aug 23 '23 07:08 kluverp