react-router - pasar accesorios al componente manejador

315

Tengo la siguiente estructura para mi aplicación React.js usando React Router :

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');

var Index = React.createClass({
  render: function () {
    return (
        <div>
            <header>Some header</header>
            <RouteHandler />
        </div>
    );
  }
});

var routes = (
  <Route path="/" handler={Index}>
    <Route path="comments" handler={Comments}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

ReactRouter.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

Quiero pasar algunas propiedades al Comments componente.

(normalmente haría esto como <Comments myprop="value" /> )

¿Cuál es la forma más fácil y correcta de hacerlo con React Router?

Kosmetika
fuente
El problema aquí, y en casos similares, especialmente con los frameworks o libs escritos en algunos idiomas, una cierta falta de medios de combinación (MoC). Las primitivas parecen estar bien en React, son bastante buenas, definiendo componentes con primitivas, en elementos React y el componente, MoC , que también parece estar bien en React. Pero los medios de combinación son incompletos. Uno debe poder pasar los accesorios a un componente mientras combina un componente con otro, no importa si coloca un componente dentro de otro componente como un elemento secundario o si pasa un componente como accesorio a otro.
Selçuk
Con alguna sintaxis como <ComponentA x={<ComponentB y={<ComponentC z={} />} />} /> OR <ComponentA x={ComponentB(ComponentC()) } /> De lo contrario, estos problemas de combinaciones de abstracciones se repetirán y necesitarán algunas soluciones menos que óptimas e indirectas llamadas soluciones alternativas como envolver, etc., etc. Las abstracciones deben ser ciudadanos de primera clase como primitivos, cualquiera que sea la percepción de primera clase.
Selçuk

Respuestas:

152

ACTUALIZAR

Desde la nueva versión, es posible pasar accesorios directamente a través del Routecomponente, sin usar un Wrapper. Por ejemplo, mediante el uso de renderprop .

Componente:

class Greeting extends React.Component {
  render() {
    const {text, match: {params}} = this.props;

    const {name} = params;

    return (
      <React.Fragment>
        <h1>Greeting page</h1>
        <p>
          {text} {name}
        </p>
      </React.Fragment>
    );
  }
}

Uso:

<Route path="/greeting/:name" render={(props) => <Greeting text="Hello, " {...props} />} />

Ejemplo de código y caja


VERSIÓN ANTIGUA

Mi forma preferida es envolver el Commentscomponente y pasar el contenedor como un controlador de ruta.

Este es su ejemplo con los cambios aplicados:

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');

var CommentsWrapper = React.createClass({
  render: function () {
    return (
      <Comments myprop="myvalue"/>
    );
  }
});

var Index = React.createClass({
  render: function () {
    return (
      <div>
        <header>Some header</header>
        <RouteHandler/>
      </div>
    );
  }
});

var routes = (
  <Route path="/" handler={Index}>
    <Route path="comments" handler={CommentsWrapper}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

ReactRouter.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});
ColCh
fuente
58
Me encuentro con el mismo problema, pero ¿esta solución no se detalla rápidamente?
captDaylight
8
De acuerdo con captDaylight, se vuelve detallado. ¡Preferiría una mejor manera de manejar esto!
Mattias Hallström
66
@mattiashallstrom IMO, la mejor manera en 1.0 es simplemente agregar la propiedad a la ruta. Ver la respuesta de Thomas E.
k00k
28
también podría agregar una sintaxis de componente sin estado (solo lambda), es bastante corta<Route path="comments" component={() => (<Comments myProp="value" />)}/>
Ciantic
33
Nunca estaré de acuerdo con crear un componente adicional solo para pasar las propiedades como la "forma preferida". Es detallado, complicado, propenso a errores y simplemente erróneo en todos los sentidos imaginables. Podría ser la ÚNICA forma en que el enrutador reaccionar lo permite, pero llamarlo "preferido" es exagerado. ¿Preferido por quién?
Szczepan Hołyszewski
260

Si prefiere no escribir envoltorios, supongo que podría hacer esto:

class Index extends React.Component { 

  constructor(props) {
    super(props);
  }
  render() {
    return (
      <h1>
        Index - {this.props.route.foo}
      </h1>
    );
  }
}

var routes = (
  <Route path="/" foo="bar" component={Index}/>
);
Thomas E
fuente
11
Esta es una respuesta correcta. En el router de reacción 1.0, puede obtener routeobjetos simples en su componente. Aquí está la respuesta del problema de github: github.com/rackt/react-router/issues/615#issuecomment-100432086
ycdesu
44
Esta es la respuesta simple que estaba buscando. Las otras técnicas funcionan, pero requieren 10 veces la cantidad de código. Funciona bien para v1.0.x. El único inconveniente que puedo ver es si tiene la intención de utilizar el mismo componente con y sin el contenedor del enrutador. Pero para mí, todos mis componentes de nivel superior mapearon 1 a 1 con rutas.
killthrush
12
¡Muchas gracias! Si alguien se pregunta, la propiedad foo estaría disponible en su componente como: this.props.route.foo
k00k
8
¿Se puede establecer esta como una respuesta correcta para evitar la confusión?
Archibald
2
No! Vea la respuesta de Rajesh Naroth para la solución real :)
Alex
114

Copiando de los comentarios de ciantic en la respuesta aceptada:

<Route path="comments" component={() => (<Comments myProp="value" />)}/>

Esta es la solución más elegante en mi opinión. Funciona. Me ayudó.

Rajesh Naroth
fuente
Esto es básicamente lo mismo que la respuesta del contenedor anterior, pero es mucho menos prolijo. Hombre, sin embargo, esa sintaxis apesta. Trate de lanzar un_ref
EdH
11
Es como un contenedor anónimo, por lo que se pierden todos los accesorios inyectados (por ejemplo, ubicación). Uno tiene que pasar manualmente los accesorios como, component={(props) => (<Comments myProp="value" location={ props.location } />)}pero todo se vuelve desordenado nuevamente
yuji
1
@JacobThomason no volvemos a procesar la configuración de enrutador de reacción, por lo que es poco probable que sea una penalización de rendimiento
Sebastien Lorber
99
A partir de React-Router 4, si proporciona una función en línea, obtendrá muchos remontes no deseados. Para la representación en línea, use el accesorio de representación. Enlace a los documentos
Daniel Reina
1
@yuji Para no hacerlo demasiado desordenado, se puede hacer: component={(props) => (<Comments {...props} myProp="value" />)}mantener los accesorios inyectados
apelsinapa
57

Esta es la solución de Rajesh , sin los inconvenientes comentados por yuji , y actualizada para React Router 4.

El código sería así:

<Route path="comments" render={(props) => <Comments myProp="value" {...props}/>}/>

Tenga en cuenta que yo uso render lugar de component. La razón es para evitar el montaje no deseado . También paso el propsmétodo y utilizo los mismos accesorios en el componente Comentarios con el operador de propagación de objetos (propuesta ES7).

Daniel Reina
fuente
¡Realmente genial para resolver el montaje no deseado también! +1
GLindqvist
44

Solo un seguimiento de la respuesta de ColCh. Es bastante fácil abstraer la envoltura de un componente:

var React = require('react');

var wrapComponent = function(Component, props) {
  return React.createClass({
    render: function() {
      return React.createElement(Component, props);
    }
  });
};

<Route path="comments" handler={wrapComponent(Comments, {myprop: value})}/>

Todavía no he probado esta solución, por lo que cualquier comentario es importante.

Es importante tener en cuenta que con este método, cualquier accesorio enviado a través del enrutador (como los parámetros) se sobrescribe / elimina.

sigmus
fuente
1
Bob, ¿estás familiarizado con los cierres? stackoverflow.com/questions/111102/…
sigmus
3
Y si necesita la consulta y los parámetros del enrutador también, entonces algo como esto funcionará: return React.createElement(Component, _.assign({}, this.props, props));(Este usa _.assign para componer el objeto combinado ... otros métodos están disponibles, por supuesto).
Malcolm Dwyer
2
Es posible que desee pasar a los niños también. var wrapComponent = function (Component, props) {return React.createClass ({render: function () {return React.createElement (Component, props, this.props.children);}}); };
Julio Rodrigues
1
Este es un componente de orden superior para aquellos que no están familiarizados con ellos facebook.github.io/react/docs/higher-order-components.html
icc97
1
NB Esto es para una versión antigua de React Router ahora. V4 actual tiene render, componenty childrenmétodos para Route. Tenga en cuenta que, como señala @dgrcode answer , debe usar en renderlugar decomponent
icc97
31

Puede pasar los accesorios pasándolos a <RouteHandler>(en v0.13.x) o al componente Route en sí en v1.0;

// v0.13.x
<RouteHandler/>
<RouteHandler someExtraProp={something}/>

// v1.0
{this.props.children}
{React.cloneElement(this.props.children, {someExtraProp: something })}

(de la guía de actualización en https://github.com/rackt/react-router/releases/tag/v1.0.0 )

Todos los manipuladores de niños recibirán el mismo conjunto de accesorios; esto puede ser útil o no dependiendo de las circunstancias.

cachvico
fuente
1
Realmente desconcierta ver que React.cloneElementse pasan múltiples elementos, pero la firma de la función parece tomar solo un elemento de reacción. Creo que este fragmento se puede hacer más fácil de entender.
manu
2
Esta es claramente la mejor respuesta en el objetivo con los documentos, pero también estoy de acuerdo con manu en que podría escribirse para mostrar un mejor uso. El código más específico para la pregunta sería: React.cloneElement(this.props.children, {myprop: "value"})o React.cloneElement(this.props.children, {myprop: this.props.myprop})etc.
juanitogan
Esta respuesta gana. También está mucho más claro lo que está sucediendo en medio de las cosas que Router hace por usted. Como alguien que lee el código, si sé que Comments está dentro de Index, miraré Index para ver qué accesorios se envían a Comments. Si sé que Comentarios es un manejador de enrutadores, miraré la configuración del enrutador y descubriré que los comentarios de los padres de Index seguirán buscando.
mjohnsonengr
24

Usando ES6 puede simplemente hacer envoltorios de componentes en línea:

<Route path="/" component={() => <App myProp={someValue}/>} >

Si necesita pasar niños:

<Route path="/" component={(props) => <App myProp={someValue}>{props.children}</App>} >

Mella
fuente
Esto es bueno, pero no pasa por los niños en caso de que haya alguno.
zpr
@zpr He agregado un ejemplo para props.children
Nick
2
Como señala @dgrcode answer , debe usar en renderlugar decomponent
icc97
23

React-router v4 alpha

ahora hay una nueva forma de hacerlo, aunque muy similar al método anterior.

import { Match, Link, Miss } from 'react-router';
import Homepage from './containers/Homepage';

const route = {
    exactly: true,
    pattern: '/',
    title: `${siteTitle} - homepage`,
    component: Homepage
  }

<Match { ...route } render={(props) => <route.component {...props} />} />

PD: esto funciona solo en la versión alfa, y se eliminaron después de la versión alfa v4. En v4 más reciente, es una vez más, con la ruta y los accesorios exactos.

react-lego, una aplicación de ejemplo contiene código que hace exactamente esto en routes.js en su rama react-router-4

Peter.mouland
fuente
21

Aquí está la solución más limpia que se me ocurrió (React Router v4):

<Route
  path="/"
  component={props => <MyComponent {...props} foo="lol" />}
/>

MyComponenttodavía tiene props.matchy props.location, y tiene props.foo === "lol".

cgenco
fuente
13

Envuélvalo con un componente de función sin estado:

<Router>
  <Route 
    path='/' 
    component={({children}) => 
      <MyComponent myProp={'myVal'}>{children}</MyComponent/>
    }/>
</Router>
mg74
fuente
12

También podría usar la combinación RouteHandler para evitar el componente envoltorio y transmitir más fácilmente el estado de los padres como accesorios:

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');
var RouteHandler = require('react-router/modules/mixins/RouteHandler');

var Index = React.createClass({
      mixins: [RouteHandler],
      render: function () {
        var handler = this.getRouteHandler({ myProp: 'value'});
        return (
            <div>
                <header>Some header</header>
                {handler}
           </div>
        );
  }
});

var routes = (
  <Route path="/" handler={Index}>
    <Route path="comments" handler={Comments}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

ReactRouter.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});
jul
fuente
1
Está expuesto públicamente en la construcción global de Bower como ReactRouter.RouteHandlerMixin, así que no lo creo.
julio
Esto también permite animar las transiciones usando TransitionGroup y CSSTransitionGroup, lo que no pude poner a trabajar con el método wrapper.
julio
2
Es extraño que no se mencione eso en los documentos oficiales.
Kosmetika
Los documentos de React ya no recomiendan los mixins: facebook.github.io/react/blog/2016/07/13/…
icc97
12

Puedes pasar accesorios de <RouterHandler/>esta manera:

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');

var Index = React.createClass({
  render: function () {
    var props = this.props; // or possibly this.state
    return (
        <div>
            <header>Some header</header>
            <RouteHandler {...props} />
        </div>
    );
  }
});

La desventaja de esto es que estás pasando accesorios indiscriminadamente. Entonces, Commentspuede terminar recibiendo accesorios que realmente están destinados a un componente diferente dependiendo de la configuración de sus rutas. No es un gran problema ya que propses inmutable, pero esto puede ser problemático si dos componentes diferentes esperan un accesorio con un nombre foopero con valores diferentes.

Meistro
fuente
1
¿Qué significan o significan los 3 puntos / puntos en este código?{...props}
Giant Elk
2
Supongo que Flux evitaría la necesidad de enviar el estado de la aplicación principal a una ruta. Obtuve el código anterior funcionando, pero no es explícito, por lo que el majic oculto es feo, no es fácil de rastrear y rastrear lo que está sucediendo.
Alce gigante
2
Difundir la explicación del operador . No es explícito, pero no es lo peor, ya que está pasando accesorios que son inmutables.
Meistro
Aquí están los documentos de ReactJS en el operador de propagación: facebook.github.io/react/docs/jsx-spread.html y también facebook.github.io/react/docs/transferring-props.html
Giant Elk
esto funcionó para mí, pero la sintaxis correcta es {... this.props}
Gana el
10

En 1.0 y 2.0 puede usar createElementprop of Routerpara especificar cómo crear exactamente su elemento de destino. Fuente de documentación

function createWithDefaultProps(Component, props) {
    return <Component {...props} myprop="value" />;
}

// and then    
<Router createElement={createWithDefaultProps}>
    ...
</Router>
Andrew Khmylov
fuente
6

También puede combinar es6 y funciones sin estado para obtener un resultado mucho más limpio:

import Dashboard from './Dashboard';
import Comments from './Comments';

let dashboardWrapper = () => <Dashboard {...props} />,
    commentsWrapper = () => <Comments {...props} />,
    index = () => <div>
        <header>Some header</header>
        <RouteHandler />
        {this.props.children}
    </div>;

routes = {
    component: index,
    path: '/',
    childRoutes: [
      {
        path: 'comments',
        component: dashboardWrapper
      }, {
        path: 'dashboard',
        component: commentsWrapper
      }
    ]
}
Zhiwei Huang
fuente
No estoy seguro exactamente de cómo funciona esto, pero se ve mal. Estás utilizando this.propsuna función, que estoy bastante seguro de que no funcionará. Si está utilizando funciones puras en lugar de extenderlas React.Component, debe pasarlas propscomo argumento, consulte los documentos React en Componentes y accesorios
icc97
6

Solución React Router v 4

Hoy me topé con esta pregunta, y aquí está el patrón que uso. Esperemos que esto sea útil para cualquiera que busque una solución más actual.

No estoy seguro de si esta es la mejor solución, pero este es mi patrón actual para esto. Por lo general, tengo un directorio Core donde guardo mis componentes de uso común con sus configuraciones relevantes (cargadores, modales, etc.), e incluyo un archivo como este:

import React from 'react'
import { Route } from 'react-router-dom'

const getLocationAwareComponent = (component) => (props) => (
  <Route render={(routeProps) => React.createElement(component, 
{...routeProps, ...props})}/>
)

export default getLocationAwareComponent

Luego, en el archivo en cuestión, haré lo siguiente:

import React from 'react'
import someComponent from 'components/SomeComponent'
import { getLocationAwareComponent } from 'components/Core/getLocationAwareComponent'
const SomeComponent = getLocationAwareComponent(someComponent)

// in render method:
<SomeComponent someProp={value} />

Notarás que importo la exportación predeterminada de mi componente como humilde camel-case, lo que me permite nombrar el nuevo componente de ubicación en CamelCase para poder usarlo normalmente. Además de la línea de importación adicional y la línea de asignación, el componente se comporta como se esperaba y recibe todos sus accesorios normalmente, con la adición de todos los accesorios de ruta. Por lo tanto, puedo redirigir felizmente desde los métodos de ciclo de vida de los componentes con this.props.history.push (), verificar la ubicación, etc.

¡Espero que esto ayude!

Chris
fuente
4

Para reaccionar router 2.x.

const WrappedComponent = (Container, propsToPass, { children }) => <Container {...propsToPass}>{children}</Container>;

y en tus rutas ...

<Route path="/" component={WrappedComponent.bind(null, LayoutContainer, { someProp })}>
</Route>

asegúrese de que la tercera parámetro es un objeto como: { checked: false }.

Holyxiaoxin
fuente
1

El problema con el React Router es que renderiza sus componentes y, por lo tanto, evita que pase los accesorios. El enrutador de navegación , por otro lado, le permite renderizar sus propios componentes. Eso significa que no tiene que saltar a través de los aros para pasar los accesorios como se muestra en el siguiente código y el JsFiddle que lo acompaña .

var Comments = ({myProp}) => <div>{myProp}</div>;

var stateNavigator = new Navigation.StateNavigator([
  {key:'comments', route:''}
]);

stateNavigator.states.comments.navigated = function(data) {
  ReactDOM.render(
    <Comments myProp="value" />,
    document.getElementById('content')
  );
}

stateNavigator.start();
Graham Mendick
fuente
1

Use el componente con o sin enrutador basado en la respuesta Rajesh Naroth.

class Index extends React.Component {

  constructor(props) {
    super(props);
  }
  render() {
    const foo = (this.props.route) ? this.props.route.foo : this.props.foo;
    return (
      <h1>
        Index - {foo}
      </h1>
    );
  }
}

var routes = (
  <Route path="/" foo="bar" component={Index}/>
);

O podrías hacerlo de esta manera:

export const Index = ({foo, route}) => {
  const content = (foo) ? foo : (route) ? route.foo : 'No content found!';
  return <h1>{content}</h1>
};
Michael Hobbs
fuente
0

para el enrutador reactivo 2.5.2, la solución es muy fácil:

    //someConponent
...
render:function(){
  return (
    <h1>This is the parent component who pass the prop to this.props.children</h1>
    {this.props.children && React.cloneElement(this.props.children,{myProp:'value'})}
  )
}
...
min mayo
fuente
0

Con un componente de ruta personalizado , esto es posible en React Router v3.

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');
var routes = (
  <Route path="/" handler={Index}>
    <MyRoute myprop="value" path="comments" handler={Comments}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

En cuanto al <MyRoute>código del componente, debería ser algo como:

import React from 'react';
import { Route } from 'react-router';
import { createRoutesFromReactChildren } from 'react-router/lib//RouteUtils';

const MyRoute = () => <div>&lt;MyRoute&gt; elements are for configuration only and should not be rendered</div>;

MyRoute.createRouteFromReactElement = (element, parentRoute) => {
    const { path, myprop } = element.props;
    // dynamically add crud route
    const myRoute = createRoutesFromReactChildren(
        <Route path={path} />,
        parentRoute
    )[0];
    // higher-order component to pass myprop as resource to components
    myRoute.component = ({ children }) => (
        <div>
            {React.Children.map(children, child => React.cloneElement(child, { myprop }))}
        </div>
    );
    return myRoute;
};

export default MyRoute;

Para obtener más detalles sobre el enfoque del componente de ruta personalizado, consulte mi publicación de blog sobre el tema: http://marmelab.com/blog/2016/09/20/custom-react-router-component.html

François Zaninotto
fuente
0

esta es probablemente la mejor manera de usar react-router-dom con un controlador de cookies

en index.js

import React, { Component } from 'react'
import {Switch,Route,Redirect} from "react-router-dom"
import {RouteWithLayout} from "./cookieCheck"

import Login from "../app/pages/login"
import DummyLayout from "../app/layouts/dummy"
import DummyPage from "../app/pages/dummy" 

export default ({props})=>{
return(
    <Switch>
        <Route path="/login" component={Login} />
        <RouteWithLayout path="/dummy" layout={DummyLayout} component={DummyPage} 
        {...props}/>
        <Redirect from="/*" to="/login" />
    </Switch>
  )
}

y use una cookie

import React , {createElement} from 'react'
import {Route,Redirect} from "react-router-dom"
import {COOKIE,getCookie} from "../services/"

export const RouteWithLayout = ({layout,component,...rest})=>{
    if(getCookie(COOKIE)==null)return <Redirect to="/login"/>
        return (
        <Route {...rest} render={(props) =>
            createElement(layout, {...props, ...rest}, createElement(component, 
      {...props, ...rest}))
       }
      />
    )
}
Snivio
fuente
0
class App extends Component {
  constructor(props){
    super(props);

    this.state = {
      data:null
    }


  }
 componentDidMount(){
   database.ref().on('value', (snapshot) =>{
     this.setState({
       data : snapshot.val()
      })
   });
 }

  render(){
  //  const { data } = this.state
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path = "/" component = { LandingPage }  />
        <Route 
          path='/signup' 
          render = { () => <Signup  data = {this.state.data} />} />
        </Switch>
    </BrowserRouter>

  );
  }
};

export default App;
nik.ss
fuente
0

Use la solución como se muestra a continuación y esto funciona en v3.2.5.

<Route
  path="/foo"
  component={() => (
    <Content
      lang="foo"
      meta={{
        description: lang_foo.description
      }}
    />
  )}
/>

o

<Route path="/foo">
  <Content
    lang="foo"
    meta={{
      description: lang_foo.description
    }}
  />
</Route>
Illvart
fuente