Rutas anidadas con react router v4 / v5

262

Actualmente estoy luchando con las rutas de anidación con react router v4.

El ejemplo más cercano fue la configuración de ruta en la documentación de React-Router v4 .

Quiero dividir mi aplicación en 2 partes diferentes.

Una interfaz y un área de administración.

Estaba pensando en algo como esto:

<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} />

La interfaz tiene un diseño y un estilo diferentes que el área de administración. Entonces, dentro de la portada, la ruta a casa, aproximadamente, y así deberían ser las rutas secundarias.

/ home debería representarse en el componente Frontpage y / admin / home debería representarse en el componente Backend.

Intenté algunas variaciones, pero siempre terminaba sin golpear / home o / admin / home.

Editar - 19.04.2017

Debido a que esta publicación tiene muchas vistas en este momento, la actualicé con la solución final. Espero que esto ayude a alguien.

Editar - 08.05.2017

Se eliminaron las soluciones antiguas.

Solución final:

Esta es la solución final que estoy usando en este momento. Este ejemplo también tiene un componente de error global como una página 404 tradicional.

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
fuente
1
¡Gracias por actualizar su pregunta con la respuesta final! solo una sugerencia: tal vez podría mantener solo la cuarta lista y la primera, ya que las otras están usando versiones obsoletas de la API y están distrayendo la respuesta
Giuliano Vilela
jajaja, no tengo idea de qué formato es esta fecha: 08.05.2017 Te sugiero que uses el formato universal ISO8601 para las fechas si no quieres confundir a las personas. es 08 el mes o el día? ISO8601 = año.mes.día hora.minuto.segundo (progresivamente más granular)
wesm
Buena solución final actualizada, pero creo que no necesitas la previousLocationlógica.
tudorpavel
¿Cuál es la motivación para reescribir completamente el router de reacción? Es mejor que sea una buena razón
Oliver Watkins
Es el enfoque declarativo. Por lo tanto, puede configurar sus rutas como utilizaría componentes de reacción.
datoml

Respuestas:

318

En react-router-v4 no anidas <Routes />. En cambio, los pones dentro de otro <Component />.


Por ejemplo

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

debe convertirse

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

con

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

Aquí hay un ejemplo básico directamente de la documentación de react-router .

Lyubomir
fuente
Puedo implementar desde su enlace en el ejemplo básico, sin embargo, cuando escribo la URL manualmente, no funciona en mi servidor localhost. pero lo hace en tu ejemplo. Por otro lado, HashRouter funciona correctamente cuando escribo la url manualmente con #. ¿Tienes alguna idea de por qué en mi servidor localhost el BrowserRouter no funciona cuando escribo la URL manualmente?
himawan_r
8
¿Puedes convertir el componente Temas en una clase? ¿Y dónde está el parámetro 'match'? en el render ()?
user1076813
21
Parece ridículo que no puedas simplemente to="exampleTopicId"con ${match.url}ser implícito.
greenimpala
8
Puede tener rutas anidadas por documentos reacttraining.com/react-router/web/example/route-config . Esto permitiría una configuración de ruta centralizada según el tema en los documentos. Piense lo loco que sería administrar en un proyecto más grande si no está disponible.
JustDave
55
Estas no son rutas anidadas, es una ruta de un nivel que todavía usa la propiedad de renderización de una ruta que toma un componente funcional como entrada, mire con más cuidado, no hay anidamiento en el sentido de enrutador de reacción <4. RouteWithSubRoutes es uno -nivel de rutas que utilizan la coincidencia de patrones.
Lyubomir
102

react-router v6

Una actualización para 2020: la próxima versión v6 tendrá Routecomponentes anidados que Just Work ™. Vea el código de ejemplo en esta publicación de blog .

Si bien la pregunta original es sobre v4 / v5 , la respuesta correcta cuando se envíe v6 será usarla si puede . Actualmente está en alfa.


react-router v4 & v5

Es cierto que para anidar rutas, debe colocarlas en el componente secundario de la ruta.

Sin embargo, si prefiere una sintaxis más en línea en lugar de dividir sus Rutas entre los componentes, puede proporcionar un componente funcional al rendersoporte de la Ruta en la que desea anidar.

<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 está interesado en por qué se renderdebe usar el accesorio, y no el componentaccesorio, es porque impide que el componente funcional en línea se vuelva a montar en cada render. Vea la documentación para más detalles.

Tenga en cuenta que el ejemplo envuelve las Rutas anidadas en un Fragmento . Antes de React 16, puedes usar un contenedor en su <div>lugar.

davnicwil
fuente
16
Gracias a Dios, la única solución que es clara, mantenible y funciona como se espera. Deseo reaccionar router 3 rutas anidadas estaban de vuelta.
Merunas Grincalaitis
este perfecto parece salida de ruta angular
Partha Ranjan
44
Deberías usar match.path, no match.url. El primero se usa generalmente en el pathaccesorio de ruta ; este último cuando se presiona una nueva ruta (por ejemplo Enlace toprop)
nbkhope
50

Solo quería mencionar que react-router v4 cambió radicalmente desde que esta pregunta fue publicada / respondida.

¡Ya no hay ningún <Match>componente! <Switch>es asegurarse de que solo se muestre la primera coincidencia. <Redirect>bueno .. redirige a otra ruta. Use o omita exactpara incluir o excluir una coincidencia parcial.

Ver los documentos. Son geniales. https://reacttraining.com/react-router/

Aquí hay un ejemplo que espero sea útil para responder a su pregunta.

<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>

Espero que haya ayudado, házmelo saber. Si este ejemplo no responde a su pregunta lo suficientemente bien, dígame y veré si puedo modificarlo.

jar0m1r
fuente
No hay exacten Redirect reacttraining.com/react-router/web/api/Redirect Eso sería mucho más limpio de lo <Route exact path="/" render={() => <Redirect to="/path" />} />que estoy haciendo en su lugar. Al menos no me deja con TypeScript.
Filuren
2
¿Entiendo correctamente que ya no hay rutas anidadas / secundarias? ¿Necesito duplicar la ruta base en todas las rutas? ¿React-router 4 no proporciona ayuda para estructurar rutas de manera sostenible?
Ville
55
@Ville estoy asombrado; ¿encontraste una mejor solución? No quiero tener rutas por todo el lugar, Dios
punkbit
1
esto funcionará, pero asegúrese de tener la ruta pública establecida en "/" en la configuración del paquete web para bundle.js; de lo contrario, las rutas anidadas no funcionarán en la actualización de la página.
Vikrant
6

Algo como esto.

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
fuente
6

Logré definir rutas anidadas envolviendo Switchy definiendo una ruta anidada antes que la ruta raíz.

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

Referencia: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md

asukiaaa
fuente
reorganizar la orden solucionó mi problema, aunque no sé si esto tendrá algún efecto secundario. pero trabajando por ahora .. gracias :)
Anbu369
2

Puedes probar algo como 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;

Creo que puedes lograr lo mismo desde aquí también.

Aniruddh Agarwal
fuente
¡buen punto! Pensé de la misma manera cuando comencé con React después del desarrollo de la aplicación de arranque Java Spring. Lo único que cambiaría es 'div' a 'Switch' en Routes.js. Y tbh, puedes definir todas las rutas en App.js pero envolverlo afuera en el archivo index.js por ejemplo (create-react-app)
Reborn
¡Sí, tiene usted razón! Lo he implementado de esta manera, por eso estoy mencionando este método.
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
fuente