Routes imbriquées avec react router v4 / v5

262

Je suis actuellement aux prises avec des routes d'imbrication utilisant le routeur React v4.

L'exemple le plus proche était la configuration de route dans la documentation React-Router v4 .

Je souhaite diviser mon application en 2 parties différentes.

Un frontend et une zone d'administration.

Je pensais à quelque chose comme ça:

<Match pattern="/" component={Frontpage}>
  <Match pattern="/home" component={HomePage} />
  <Match pattern="/about" component={AboutPage} />
</Match>
<Match pattern="/admin" component={Backend}>
  <Match pattern="/home" component={Dashboard} />
  <Match pattern="/users" component={UserPage} />
</Match>
<Miss component={NotFoundPage} />

Le frontend a une disposition et un style différents de ceux de la zone d'administration. Ainsi, en première page, l'itinéraire vers la maison, environ et donc l'un devrait être les itinéraires enfants.

/ home doit être rendu dans le composant Frontpage et / admin / home doit être rendu dans le composant Backend.

J'ai essayé quelques variantes mais j'ai toujours fini par ne pas frapper / home ou / admin / home.

Éditer - 19.04.2017

Parce que ce message a beaucoup de vues en ce moment, je l'ai mis à jour avec la solution finale. J'espère que ça aide quelqu'un.

Éditer - 08.05.2017

Suppression des anciennes solutions

Solution finale:

Ceci est la solution finale que j'utilise en ce moment. Cet exemple a également un composant d'erreur global comme une page 404 traditionnelle.

import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';

const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>

const Frontend = props => {
  console.log('Frontend');
  return (
    <div>
      <h2>Frontend</h2>
      <p><Link to="/">Root</Link></p>
      <p><Link to="/user">User</Link></p>
      <p><Link to="/admin">Backend</Link></p>
      <p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
      <Switch>
        <Route exact path='/' component={Home}/>
        <Route path='/user' component={User}/>
        <Redirect to={{
          state: { error: true }
        }} />
      </Switch>
      <footer>Bottom</footer>
    </div>
  );
}

const Backend = props => {
  console.log('Backend');
  return (
    <div>
      <h2>Backend</h2>
      <p><Link to="/admin">Root</Link></p>
      <p><Link to="/admin/user">User</Link></p>
      <p><Link to="/">Frontend</Link></p>
      <p><Link to="/admin/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
      <Switch>
        <Route exact path='/admin' component={Home}/>
        <Route path='/admin/user' component={User}/>
        <Redirect to={{
          state: { error: true }
        }} />
      </Switch>
      <footer>Bottom</footer>
    </div>
  );
}

class GlobalErrorSwitch extends Component {
  previousLocation = this.props.location

  componentWillUpdate(nextProps) {
    const { location } = this.props;

    if (nextProps.history.action !== 'POP'
      && (!location.state || !location.state.error)) {
        this.previousLocation = this.props.location
    };
  }

  render() {
    const { location } = this.props;
    const isError = !!(
      location.state &&
      location.state.error &&
      this.previousLocation !== location // not initial render
    )

    return (
      <div>
        {          
          isError
          ? <Route component={Error} />
          : <Switch location={isError ? this.previousLocation : location}>
              <Route path="/admin" component={Backend} />
              <Route path="/" component={Frontend} />
            </Switch>}
      </div>
    )
  }
}

class App extends Component {
  render() {
    return <Route component={GlobalErrorSwitch} />
  }
}

export default App;
datoml
la source
1
Merci d'avoir mis à jour votre question avec la réponse finale! juste une suggestion: vous ne pourriez peut-être conserver que la 4ème liste et la première, car les autres utilisent des versions obsolètes de l'API et distraient de la réponse
Giuliano Vilela
lol, je n'ai aucune idée du format de cette date: 08.05.2017 Je vous suggère d'utiliser le format universel ISO8601 pour les dates si vous ne voulez pas confondre les gens. est 08 le mois ou le jour? ISO8601 = year.month.day hour.minute.second (progressivement plus granulaire)
wesm
Belle solution finale mise à jour, mais je pense que vous n'avez pas besoin de la previousLocationlogique.
tudorpavel
quelle est la motivation pour réécrire complètement le routeur de réaction. Il vaut mieux être une bonne raison
Oliver Watkins
C'est l'approche déclarative. Vous pouvez donc configurer vos routages comme vous utiliseriez des composants React.
datoml

Réponses:

318

Dans react-router-v4, vous ne vous imbriquez pas <Routes />. Au lieu de cela, vous les mettez dans un autre <Component />.


Par exemple

<Route path='/topics' component={Topics}>
  <Route path='/topics/:topicId' component={Topic} />
</Route>

devrait devenir

<Route path='/topics' component={Topics} />

avec

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <Link to={`${match.url}/exampleTopicId`}>
      Example topic
    </Link>
    <Route path={`${match.path}/:topicId`} component={Topic}/>
  </div>
) 

Voici un exemple de base directement issu de la documentation de react-router .

Lyubomir
la source
je suis en mesure de mettre en œuvre à partir de votre lien dans l'exemple de base mais quand je tape l'URL manuellement, cela ne fonctionne pas sur mon serveur localhost. mais c'est le cas sur votre exemple. D'autre part, HashRouter fonctionne correctement lorsque je tape l'URL manuellement avec #. Avez-vous une idée pourquoi sur mon serveur localhost le BrowserRouter ne fonctionne pas quand je tape l'URL manuellement?
himawan_r
8
pouvez-vous faire du composant Sujets une classe? et où est entré le paramètre 'match'? dans le rendu ()?
user1076813
21
Semble ridicule vous ne pouvez pas to="exampleTopicId"avec l' ${match.url}être implicite.
greenimpala
8
Vous pouvez avoir des itinéraires imbriqués par document reacttraining.com/react-router/web/example/route-config . Cela permettrait une configuration d'itinéraire centralisée selon le sujet dans les documents. Pensez à quel point cela serait insensé de gérer un projet plus vaste s'il n'est pas disponible.
JustDave
5
Ce ne sont pas des routes imbriquées, c'est un routage à un niveau utilisant toujours la propriété de rendu d'une route qui prend un composant fonctionnel comme entrée, regardez plus attentivement, il n'y a pas d'imbrication dans le sens de réagir routeur <4. RouteWithSubRoutes est un -niveau liste des routes qui utilisent la correspondance de modèles.
Lyubomir
102

react-router v6

Une mise à jour pour 2020: la prochaine version v6 aura des Routecomposants imbriqués Just Work ™. Voir l'exemple de code dans cet article de blog .

Alors que la question d'origine concerne la v4 / v5 , la bonne réponse lorsque les vaisseaux v6 seront utilisés si vous le pouvez . Il est actuellement en alpha.


react-router v4 & v5

Il est vrai que pour imbriquer des routes, vous devez les placer dans le composant enfant de la route.

Cependant, si vous préférez une syntaxe plus en ligne plutôt que de diviser vos itinéraires en plusieurs composants, vous pouvez fournir un composant fonctionnel à l' renderaccessoire de l'itinéraire sous lequel vous souhaitez imbriquer.

<BrowserRouter>

  <Route path="/" component={Frontpage} exact />
  <Route path="/home" component={HomePage} />
  <Route path="/about" component={AboutPage} />

  <Route
    path="/admin"
    render={({ match: { url } }) => (
      <>
        <Route path={`${url}/`} component={Backend} exact />
        <Route path={`${url}/home`} component={Dashboard} />
        <Route path={`${url}/users`} component={UserPage} />
      </>
    )}
  />

</BrowserRouter>

Si vous êtes intéressé par la raison pour laquelle l' renderhélice doit être utilisée, et non par l' componenthélice, c'est parce qu'elle empêche le composant fonctionnel en ligne d'être remonté sur chaque rendu. Voir la documentation pour plus de détails.

Notez que l'exemple enveloppe les routes imbriquées dans un fragment . Avant React 16, vous pouvez utiliser un conteneur à la <div>place.

davnicwil
la source
16
Dieu merci, la seule solution qui soit claire, maintenable et fonctionne comme prévu. Je souhaite que les routes imbriquées du routeur 3 soient de retour.
Merunas Grincalaitis
celui-ci parfait ressemble à une sortie de route angulaire
Partha Ranjan
4
Vous devez utiliser match.path, non match.url. Le premier est généralement utilisé dans l' pathhélice Route ; ce dernier lorsque vous poussez un nouvel itinéraire (par exemple Link toprop)
nbkhope
50

Je voulais juste mentionner que react-router v4 a changé radicalement depuis que cette question a été publiée / répondue.

Il n'y a plus de <Match>composant! <Switch>est de s'assurer que seule la première correspondance est rendue. <Redirect>bien .. redirige vers un autre itinéraire. Utilisez ou omettez exactpour inclure ou exclure une correspondance partielle.

Voir les documents. Ils sont grands. https://reacttraining.com/react-router/

Voici un exemple que j'espère utilisable pour répondre à votre question.

<Router>
  <div>
    <Redirect exact from='/' to='/front'/>
    <Route path="/" render={() => {
      return (
        <div>
          <h2>Home menu</h2>
          <Link to="/front">front</Link>
          <Link to="/back">back</Link>
        </div>
      );
    }} />          
    <Route path="/front" render={() => {
      return (
        <div>
        <h2>front menu</h2>
        <Link to="/front/help">help</Link>
        <Link to="/front/about">about</Link>
        </div>
      );
    }} />
    <Route exact path="/front/help" render={() => {
      return <h2>front help</h2>;
    }} />
    <Route exact path="/front/about" render={() => {
      return <h2>front about</h2>;
    }} />
    <Route path="/back" render={() => {
      return (
        <div>
        <h2>back menu</h2>
        <Link to="/back/help">help</Link>
        <Link to="/back/about">about</Link>
        </div>
      );
    }} />
    <Route exact path="/back/help" render={() => {
      return <h2>back help</h2>;
    }} />
    <Route exact path="/back/about" render={() => {
      return <h2>back about</h2>;
    }} />
  </div>
</Router>

J'espère que cela a aidé, faites le moi savoir. Si cet exemple ne répond pas assez bien à votre question, dites-le moi et je verrai si je peux le modifier.

jar0m1r
la source
Il n'y en a pas exactsur Redirect reacttraining.com/react-router/web/api/Redirect Ce serait beaucoup plus propre que ce <Route exact path="/" render={() => <Redirect to="/path" />} />que je fais à la place. Au moins, cela ne me laissera pas avec TypeScript.
Filuren
2
Dois-je comprendre correctement que les routes imbriquées / secondaires n'existent plus (plus)? dois-je dupliquer l'itinéraire de base dans tous les itinéraires? React-router 4 ne fournit-il aucune aide pour structurer les routes de manière maintenable?
Ville
5
@Ville je suis étonné; avez-vous trouvé une meilleure solution? Je ne veux pas avoir de routes partout,
ça alors
1
cela fonctionnera mais assurez-vous que le chemin public est défini sur "/" dans la configuration du webpack pour bundle.js, sinon les routes imbriquées ne fonctionneront pas lors de l'actualisation de la page.
vikrant
6

Quelque chose comme ça.

import React from 'react';
import {
  BrowserRouter as Router, Route, NavLink, Switch, Link
} from 'react-router-dom';

import '../assets/styles/App.css';

const Home = () =>
  <NormalNavLinks>
    <h1>HOME</h1>
  </NormalNavLinks>;
const About = () =>
  <NormalNavLinks>
    <h1>About</h1>
  </NormalNavLinks>;
const Help = () =>
  <NormalNavLinks>
    <h1>Help</h1>
  </NormalNavLinks>;

const AdminHome = () =>
  <AdminNavLinks>
    <h1>root</h1>
  </AdminNavLinks>;

const AdminAbout = () =>
  <AdminNavLinks>
    <h1>Admin about</h1>
  </AdminNavLinks>;

const AdminHelp = () =>
  <AdminNavLinks>
    <h1>Admin Help</h1>
  </AdminNavLinks>;


const AdminNavLinks = (props) => (
  <div>
    <h2>Admin Menu</h2>
    <NavLink exact to="/admin">Admin Home</NavLink>
    <NavLink to="/admin/help">Admin Help</NavLink>
    <NavLink to="/admin/about">Admin About</NavLink>
    <Link to="/">Home</Link>
    {props.children}
  </div>
);

const NormalNavLinks = (props) => (
  <div>
    <h2>Normal Menu</h2>
    <NavLink exact to="/">Home</NavLink>
    <NavLink to="/help">Help</NavLink>
    <NavLink to="/about">About</NavLink>
    <Link to="/admin">Admin</Link>
    {props.children}
  </div>
);

const App = () => (
  <Router>
    <div>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/help" component={Help}/>
        <Route path="/about" component={About}/>

        <Route exact path="/admin" component={AdminHome}/>
        <Route path="/admin/help" component={AdminHelp}/>
        <Route path="/admin/about" component={AdminAbout}/>
      </Switch>

    </div>
  </Router>
);


export default App;

Sanjeev Shakya
la source
6

J'ai réussi à définir des routes imbriquées en encapsulant Switchet en définissant une route imbriquée avant la route racine.

<BrowserRouter>
  <Switch>
    <Route path="/staffs/:id/edit" component={StaffEdit} />
    <Route path="/staffs/:id" component={StaffShow} />
    <Route path="/staffs" component={StaffIndex} />
  </Switch>
</BrowserRouter>

Référence: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md

asukiaaa
la source
réorganiser l'ordre a résolu mon problème, même si je ne sais pas si cela aura des effets secondaires. mais travaille pour l'instant .. merci :)
Anbu369
2

Vous pouvez essayer quelque chose comme Routes.js

import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom';
import FrontPage from './FrontPage';
import Dashboard from './Dashboard';
import AboutPage from './AboutPage';
import Backend from './Backend';
import Homepage from './Homepage';
import UserPage from './UserPage';
class Routes extends Component {
    render() {
        return (
            <div>
                <Route exact path="/" component={FrontPage} />
                <Route exact path="/home" component={Homepage} />
                <Route exact path="/about" component={AboutPage} />
                <Route exact path="/admin" component={Backend} />
                <Route exact path="/admin/home" component={Dashboard} />
                <Route exact path="/users" component={UserPage} />    
            </div>
        )
    }
}

export default Routes

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Routes from './Routes';

class App extends Component {
  render() {
    return (
      <div className="App">
      <Router>
        <Routes/>
      </Router>
      </div>
    );
  }
}

export default App;

Je pense que vous pouvez réaliser la même chose à partir d'ici aussi.

Aniruddh Agarwal
la source
bon point! J'ai pensé de la même manière lorsque j'ai commencé avec React après le développement de l'application de démarrage Java Spring. la seule chose que je changerais est «div» en «Switch» dans Routes.js. Et tbh, vous pouvez définir toutes les routes dans App.js mais les envelopper à l'extérieur dans le fichier index.js par exemple (create-react-app)
Reborn
Oui, tu as raison! J'ai mis en œuvre de cette façon, c'est pourquoi je mentionne cette méthode.
Aniruddh Agarwal
-6
interface IDefaultLayoutProps {
    children: React.ReactNode
}

const DefaultLayout: React.SFC<IDefaultLayoutProps> = ({children}) => {
    return (
        <div className="DefaultLayout">
            {children}
        </div>
    );
}


const LayoutRoute: React.SFC<IDefaultLayoutRouteProps & RouteProps> = ({component: Component, layout: Layout, ...rest}) => {
const handleRender = (matchProps: RouteComponentProps<{}, StaticContext>) => (
        <Layout>
            <Component {...matchProps} />
        </Layout>
    );

    return (
        <Route {...rest} render={handleRender}/>
    );
}

const ScreenRouter = () => (
    <BrowserRouter>
        <div>
            <Link to="/">Home</Link>
            <Link to="/counter">Counter</Link>
            <Switch>
                <LayoutRoute path="/" exact={true} layout={DefaultLayout} component={HomeScreen} />
                <LayoutRoute path="/counter" layout={DashboardLayout} component={CounterScreen} />
            </Switch>
        </div>
    </BrowserRouter>
);
EVGENY GLUKHOV
la source