Router: [Question] Enforcing the path of a default route

Created on 13 Jun 2018  路  13Comments  路  Source: reach/router

Hello,

I want to enforce my paths when using a default route. (No "NotFound" page, but a redirect to the "default" location in the tree)

For example I have this structure (I assume it could be a nested router too so it should work relatively) :

<Router>
  <UserScreen path="user" />
  <MarketScreen default path="market" />
</Router>

Here when no route is matched, MarketScreen is rendered, but its path isn't used. I'd like the "/market" part to be pushed into history and displayed in the address bar too.

I tried using <Redirect/> too to achieve this behaviour but couldn't find a working combination.

Is it possible to achieve with the current API?

question

Most helpful comment

I wasn't able to get the <Redirect /> update to work with a relative path. However, I was able to build a <NoMatch /> that accomplishes this functionality.

NoMatch (TypeScript)

import React from 'react';
import { RouteComponentProps, Redirect } from '@reach/router';

interface Props extends RouteComponentProps {
  to: string;
  isRelative?: boolean;
  noThrow?: boolean;
}

export const NoMatch = ({
  to,
  isRelative = false,
  noThrow = false,
  uri,
}: Props) => {
  if (uri == null) return null;

  const cleanedURI = uri.slice(-1) === '/' ? uri.slice(0, uri.length - 1) : uri;

  return (
    <Redirect
      noThrow={noThrow}
      from="/"
      to={isRelative === true ? `${cleanedURI}/${to}` : to}
    />
  );
};

usage:

import React from 'react';
import { Router } from '@reach/router';

import { UsersRoute } from './UsersRoute';
import { NoMatch } from './NoMatch';

const Router = () => (
  <Router>
    <UsersRoute path="users" />

    <NoMatch default isRelative noThrow to="users" />
  </Router>
);

All 13 comments

This should work:

<Router>
  <UserScreen path="user" />
  <MarketScreen path="market" />
  <Redirect default noThrow to="/market" />
</Router>

Hi @codepunkt ,

Unfortunatly I already tried this.

Redirect doesn't seem to work with relative links, so I lose the ability to "isolate" my sub-screens.

In my case I have something like this :

// Main screen
<Router>
        <Redirect noThrow default to="colisage/*" />
        <ColisageScreen path="colisage/*" />
</Router>

// Inside ColisageScreen, there is a sub-router
<Router>
        <Redirect noThrow default to="enregistrer-tube" />
        <EnregistrerTubeTab path="enregistrer-tube" />
        <ChargerPortoirTab path="charger-portoir" />
        <ChargerMalletteTab path="charger-mallette" />
        <DechargerMalletteTab path="decharger-mallette" />
        <DechargerPortoirTab path="decharger-portoir" />
</Router>

So when I type the URL "/whatever" in my app, I'd like it to display : "/colisage/enregistrer-tube" as default.

Does it make sense?

You can use this to redirect when they land at the root:

<Redirect noThrow from="/" to="enregistrer-tube" />

Redirect support default is kind of interesting, but are you sure you don't want to tell them the URL they used isn't valid?

@ryanflorence I set up a codesandbox illustrating my example : https://codesandbox.io/s/mojmyv4jzy

Redirect support default is kind of interesting, but are you sure you don't want to tell them the URL they used isn't valid?

Yes I'm sure of it 馃槃

In fact what I miss here is being able to use relative path in Redirect's to prop when using it inside a Router so the subtree don't have to know about the parent's path context.

This is the interesting part :

<Router>
      {/*
      I'd expect this Redirect to redirect to the sibling route FirstTab
      using one of these syntax :
      <Redirect noThrow from="*" to="first" />
      <Redirect noThrow default to="first" />
      <Redirect noThrow from="*" to="./first" />
      <Redirect noThrow default to="./first" />
      */}
      <Redirect noThrow from="*" to="main/first" />
      <FirstTab path="first" />
      <SecondTab path="second" />
</Router>

The most consistent syntax would be <Redirect noThrow default to="first" /> IMO because from="*" could be confusing (we could think it won't work because trying to reach second would always redirect to first) and to="first" is more consistent with the relative syntax you adopted in the API than to="./first".

for now your <NoMatch/> can render a <Redirect to="/wherever"/>

<NoMatch default/>

let NoMatch = () => <Redirect to="/wherever"/>

I agree with @jgoux the Redirect not working with relative links is quite confusing (especially if you provide basePath explicitely)

<Router basepath="/auth">
  <Redirect from="/" to="login" />
  <Login path="login" />
  <Authenticate path="callback" />
  <Logout path="logout" />
</Router>

When I visit /auth I expect to be redirected to /auth/login and not /login

Related? #94

Yes I think this should solve my use case. 馃槂

@ryanflorence

Wait, if Redirect is going to support relative links, than this:

<Redirect to="login" />

should be trivial, no? In order to resolve the relative link you need to know the path, somehow. Which means, if the above works, that you have the path. Which, in turn, means you could inject from automatically to be the closest predecessor path

Am I off here?

If this is merge, can we close this issue?

I wasn't able to get the <Redirect /> update to work with a relative path. However, I was able to build a <NoMatch /> that accomplishes this functionality.

NoMatch (TypeScript)

import React from 'react';
import { RouteComponentProps, Redirect } from '@reach/router';

interface Props extends RouteComponentProps {
  to: string;
  isRelative?: boolean;
  noThrow?: boolean;
}

export const NoMatch = ({
  to,
  isRelative = false,
  noThrow = false,
  uri,
}: Props) => {
  if (uri == null) return null;

  const cleanedURI = uri.slice(-1) === '/' ? uri.slice(0, uri.length - 1) : uri;

  return (
    <Redirect
      noThrow={noThrow}
      from="/"
      to={isRelative === true ? `${cleanedURI}/${to}` : to}
    />
  );
};

usage:

import React from 'react';
import { Router } from '@reach/router';

import { UsersRoute } from './UsersRoute';
import { NoMatch } from './NoMatch';

const Router = () => (
  <Router>
    <UsersRoute path="users" />

    <NoMatch default isRelative noThrow to="users" />
  </Router>
);

Thanks @SavePointSam !

Maybe it's a little late, but I attach my humble solution:

`class RelativeRedirect extends React.Component{

render(){
    let { to, ...rest } = this.props;
    to = to.replace('/', '');
    const pathname = this.props.location.pathname;
    return <Redirect {...rest} to={`${pathname}/${to}`}/>;
}

}`

And in the Router:

<Families path={"families"}>
   <RelativeRedirect from={"/"} to={"list"} default noThrow />
<Families/>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

sseppola picture sseppola  路  4Comments

maieonbrix picture maieonbrix  路  4Comments

hardfist picture hardfist  路  3Comments

danoc picture danoc  路  3Comments

michaelwiktorek picture michaelwiktorek  路  4Comments