react-router - passer les accessoires au composant gestionnaire

315

J'ai la structure suivante pour mon application React.js utilisant 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);
});

Je souhaite transmettre certaines propriétés au Commentscomposant.

(normalement je ferais ça comme <Comments myprop="value" />)

Quelle est la manière la plus simple et la plus appropriée de le faire avec React Router?

Kosmetika
la source
Le problème ici, et dans des cas similaires, notamment avec les frameworks ou libs écrits dans certaines langues, un certain manque de moyens de combinaison (MoC). Les primitives semblent correctes dans React, elles sont plutôt bonnes, définissant des composants avec des primitives, dans les éléments React et le composant, MoC , qui semble correct également dans React. Mais les moyens de combinaison sont incomplets. Il faut être capable de passer les accessoires à un composant tout en combinant un composant à un autre, peu importe que ce soit en plaçant un composant dans un autre composant en tant qu'enfant de celui-ci ou en passant un composant en tant qu'accessoire à un autre.
Selçuk
Avec une syntaxe comme <ComponentA x={<ComponentB y={<ComponentC z={} />} />} /> OR <ComponentA x={ComponentB(ComponentC()) } /> Sinon, ces problèmes de combinaisons d'abstractions se reproduiront et nécessiteront des solutions moins qu'optimales et indirectes appelées solutions de contournement comme l'emballage, etc., etc. Les abstractions doivent être des citoyens de première classe en tant que primitifs, quelle que soit la perception de première classe.
Selçuk

Réponses:

152

METTRE À JOUR

Depuis la nouvelle version, il est possible de passer des accessoires directement via le Routecomposant, sans utiliser de Wrapper. Par exemple, en utilisant renderprop .

Composant:

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>
    );
  }
}

Usage:

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

Exemple de code et de boîte


ANCIENNE VERSION

Ma façon préférée est d'envelopper le Comments composant et de passer l'encapsuleur en tant que gestionnaire d'itinéraire.

Voici votre exemple avec des changements appliqués:

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
la source
58
Je rencontre le même problème, mais cette solution n'est-elle pas verbeuse rapidement?
captDaylight
8
D'accord avec captDaylight, il devient verbeux. Je préférerais une meilleure façon de gérer cela!
Mattias Hallström
6
@mattiashallstrom IMO, la meilleure façon dans 1.0 est d'ajouter simplement la propriété à l'itinéraire. Voir la réponse de Thomas E.
k00k
28
vous pouvez également ajouter une syntaxe de composant sans état (juste lambda), c'est assez court<Route path="comments" component={() => (<Comments myProp="value" />)}/>
Ciantic
33
Je ne serai jamais d'accord avec la création d'un composant supplémentaire juste pour passer les propriétés étant la "manière préférée". C'est verbeux, compliqué, sujet aux erreurs et tout simplement faux à tous points de vue imaginables. C'est peut-être la SEULE façon dont le routeur réagit le permet, mais l'appeler "préféré" est un tronçon. Préféré par qui?
Szczepan Hołyszewski
260

Si vous préférez ne pas écrire de wrappers, je suppose que vous pouvez le faire:

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
la source
11
Ceci est une bonne réponse. Dans le react-router 1.0, vous pouvez obtenir routeun objet simple dans votre composant. Voici la réponse du problème github: github.com/rackt/react-router/issues/615#issuecomment-100432086
ycdesu
4
C'est la réponse simple que je cherchais. Les autres techniques fonctionnent, mais nécessitent 10 fois plus de code. Fonctionne bien pour v1.0.x. Le seul inconvénient que je peux voir est que vous avez l'intention d'utiliser le même composant avec et sans le conteneur de routeur. Mais pour moi, tous mes composants de niveau supérieur ont mappé 1 à 1 avec des itinéraires.
killthrush
12
Grand merci! Si quelqu'un se pose la question, la propriété foo serait disponible dans votre composant en tant que: this.props.route.foo
k00k
8
Celui-ci peut-il être défini comme une réponse correcte pour éviter la confusion?
Archibald
2
Nan! Voir la réponse de Rajesh Naroth pour la vraie solution :)
Alex
114

Copie des commentaires de ciantic dans la réponse acceptée:

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

C'est la solution la plus gracieuse à mon avis. Ça marche. M'a aidé.

Rajesh Naroth
la source
C'est fondamentalement la même que la réponse wrapper ci-dessus, mais c'est beaucoup moins verbeux. Mais l'homme, cette syntaxe craint. Essayez de jeter un_ref
EdH
11
C'est comme un wrapper anonyme, donc tous les accessoires injectés (par exemple l'emplacement) sont manqués. Il faut passer manuellement les accessoires comme, component={(props) => (<Comments myProp="value" location={ props.location } />)}mais tout devient de nouveau en désordre
yuji
1
@JacobThomason, nous ne restituons pas la configuration du routeur React, il est donc peu probable que cela affecte les performances.
Sebastien Lorber
9
À partir de React-Router 4, si vous fournissez une fonction en ligne, vous obtiendrez beaucoup de remontages indésirables. Pour le rendu en ligne, utilisez l'accessoire de rendu. Lien vers les documents
Daniel Reina
1
@yuji Pour ne pas le rendre trop salissant, on peut faire: component={(props) => (<Comments {...props} myProp="value" />)}maintenir les accessoires injectés
apelsinapa
57

Il s'agit de la solution de Rajesh , sans les inconvénients commentés par yuji , et mise à jour pour React Router 4.

Le code serait comme ceci:

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

Notez que j'utilise à la renderplace de component. La raison est d'éviter un remontage indésirable . Je passe également le propsà cette méthode, et j'utilise les mêmes accessoires sur le composant Commentaires avec l'opérateur de propagation d'objet (proposition ES7).

Daniel Reina
la source
Vraiment très bien pour résoudre le montage indésirable aussi! +1
GLindqvist
44

Juste un suivi de la réponse de ColCh. Il est assez facile d'abstraire l'habillage d'un composant:

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

Je n'ai pas encore testé cette solution, donc tout commentaire est important.

Il est important de noter qu'avec cette méthode, tous les accessoires envoyés via le routeur (tels que les paramètres) sont remplacés / supprimés.

sigmus
la source
1
Bob, connaissez-vous les fermetures? stackoverflow.com/questions/111102/…
sigmus
3
Et si vous avez également besoin de la requête et des paramètres du routeur, quelque chose comme ça fonctionnera: return React.createElement(Component, _.assign({}, this.props, props));(Celui-ci utilise _.assign pour composer l'objet combiné ... d'autres méthodes sont bien sûr disponibles).
Malcolm Dwyer
2
Vous voudrez peut-être aussi passer les enfants. var wrapComponent = function (Component, props) {return React.createClass ({render: function () {return React.createElement (Component, props, this.props.children);}}); };
Julio Rodrigues
1
Il s'agit d'un composant d'ordre supérieur pour ceux qui ne les connaissent pas facebook.github.io/react/docs/higher-order-components.html
icc97
1
NB C'est pour une ancienne version de React Router maintenant. V4 actuelle a render, componentet les childrenméthodes pour Route. Notez que comme l' indique la réponse @dgrcode , vous devez utiliser à la renderplace decomponent
icc97
31

Vous pouvez passer des accessoires en les passant à <RouteHandler>(dans la v0.13.x) ou au composant Route lui-même dans la v1.0;

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

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

(à partir du guide de mise à niveau à https://github.com/rackt/react-router/releases/tag/v1.0.0 )

Tous les gestionnaires d'enfants recevront le même ensemble d'accessoires - cela peut être utile ou non selon les circonstances.

cachvico
la source
1
Il est vraiment difficile de voir React.cloneElementpasser plusieurs éléments, mais la signature de la fonction semble ne prendre qu'un seul élément de réaction. Je pense que cet extrait peut être rendu plus facile à comprendre.
manu
2
C'est clairement la meilleure réponse à la cible avec les documents, mais je suis également d'accord avec manu qu'il pourrait être écrit pour montrer une meilleure utilisation. Un code plus spécifique à la question ressemblerait à: React.cloneElement(this.props.children, {myprop: "value"})ou React.cloneElement(this.props.children, {myprop: this.props.myprop})etc.
juanitogan
Cette réponse gagne. Il est également beaucoup plus clair de savoir ce qui se passe au milieu de ce que le routeur fait pour vous. En tant que personne lisant le code, si je sais que Comments est dans Index, je vais regarder Index pour voir quels accessoires sont envoyés aux commentaires. S'il m'arrive de savoir que Commentaires est un gestionnaire de routeur, je vais regarder la configuration du routeur et découvrir que les parents Index indexent les commentaires et continuer à y chercher.
mjohnsonengr
24

En utilisant ES6, vous pouvez simplement rendre les wrappers de composants en ligne:

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

Si vous devez passer des enfants:

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

pseudo
la source
C'est bien mais ça ne passe pas par les enfants au cas où il y en aurait.
zpr
@zpr J'ai ajouté un exemple pour props.children
Nick
2
Comme le souligne la réponse @dgrcode , vous devez utiliser à la renderplace decomponent
icc97
23

React-router v4 alpha

maintenant, il existe une nouvelle façon de le faire, bien que très similaire à la méthode précédente.

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

PS Cela ne fonctionne que dans la version alpha et a été supprimé après la version alpha v4. Dans la dernière version v4, c'est encore une fois, avec le chemin et les accessoires exacts.

react-lego un exemple d'application contient du code qui fait exactement cela dans routes.js sur sa branche react-router-4

peter.mouland
la source
21

Voici la solution la plus propre que j'ai trouvée (React Router v4):

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

MyComponenta encore props.matchet props.location, et a props.foo === "lol".

cgenco
la source
13

Enveloppez-le avec un composant de fonction sans état:

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

Vous pouvez également utiliser le mixin RouteHandler pour éviter le composant wrapper et transmettre plus facilement l'état du parent comme accessoires:

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);
});
juil
la source
1
Il est publiquement exposé sur la construction mondiale de bower en tant que ReactRouter.RouteHandlerMixin, donc je ne pense pas.
jul
Cela permet également d'animer des transitions à l'aide de TransitionGroup et CSSTransitionGroup que je n'ai pas pu utiliser en utilisant la méthode wrapper.
jul
2
Il est étrange que cela ne soit pas mentionné dans les documents officiels.
Kosmetika
Les mixins ne sont plus recommandés par les documents React: facebook.github.io/react/blog/2016/07/13/…
icc97
12

Vous pouvez passer des accessoires via <RouterHandler/>comme ceci:

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>
    );
  }
});

L'inconvénient est que vous passez des accessoires sans discernement. CommentsCela peut donc finir par recevoir des accessoires qui sont vraiment destinés à un composant différent en fonction de la configuration de vos itinéraires. Ce n'est pas énorme car il propsest immuable, mais cela peut être problématique si deux composants différents attendent un accessoire nommé foomais avec des valeurs différentes.

Meistro
la source
1
Que signifient ou signifient les 3 points / points dans ce code:{...props}
Giant Elk
2
Je suppose que Flux éviterait d'avoir à envoyer l'état de l'application parent vers un itinéraire. J'ai fait fonctionner le code ci-dessus, mais ce n'est pas explicite, donc le majic caché est laid, pas facile à tracer et à suivre ce qui se passe.
Giant Elk
2
Répandre l'explication de l'opérateur . Ce n'est pas explicite mais ce n'est pas la pire des choses puisque vous passez des accessoires immuables.
Meistro
Voici les documents ReactJS sur l'opérateur de propagation: facebook.github.io/react/docs/jsx-spread.html et aussi facebook.github.io/react/docs/transferring-props.html
Giant Elk
cela a fonctionné pour moi, mais la syntaxe correcte est {... this.props}
Win
10

Dans 1.0 et 2.0, vous pouvez utiliser createElementprop of Routerpour spécifier comment créer exactement votre élément cible. Source de documentation

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

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

Vous pouvez également combiner les fonctions es6 et sans état pour obtenir un résultat beaucoup plus net :

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
la source
Je ne sais pas exactement comment cela fonctionne - mais cela semble faux. Vous utilisez this.propsune fonction qui, je suis sûr, ne fonctionnera pas. Si vous utilisez des fonctions pures au lieu d'étendre le, React.Componentvous devez passer propscomme argument, consultez la documentation React sur les composants et les accessoires
icc97
6

Solution React Router v 4

Je suis tombé sur cette question plus tôt dans la journée, et voici le modèle que j'utilise. J'espère que cela sera utile à tous ceux qui recherchent une solution plus actuelle.

Je ne sais pas si c'est la meilleure solution, mais c'est mon schéma actuel pour cela. J'ai généralement un répertoire Core où je conserve mes composants couramment utilisés avec leurs configurations pertinentes (chargeurs, modaux, etc.), et j'inclus un fichier comme celui-ci:

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

Ensuite, dans le fichier en question, je ferai ce qui suit:

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

Vous remarquerez que j'importe l'exportation par défaut de mon composant comme humble cas de chameau, ce qui me permet de nommer le nouveau composant de localisation dans CamelCase afin que je puisse l'utiliser normalement. Outre la ligne d'importation supplémentaire et la ligne d'affectation, le composant se comporte comme prévu et reçoit tous ses accessoires normalement, avec l'ajout de tous les accessoires d'itinéraire. Ainsi, je peux joyeusement rediriger à partir des méthodes de cycle de vie des composants avec this.props.history.push (), vérifier l'emplacement, etc.

J'espère que cela t'aides!

Chris
la source
4

Pour react router 2.x.

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

et dans vos itinéraires ...

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

assurez - vous que le 3ème est un objet param comme: { checked: false }.

Holyxiaoxin
la source
1

Le problème avec le React Router est qu'il rend vos composants et vous empêche donc de passer des accessoires. Le routeur de navigation , d'autre part, vous permet de rendre vos propres composants. Cela signifie que vous n'avez pas à sauter à travers des cerceaux pour passer dans les accessoires comme le code suivant et le spectacle JsFiddle qui l' accompagne .

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
la source
1

Utilisez le composant avec ou sans routeur basé sur la réponse de 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}/>
);

Ou vous pourriez le faire de cette façon:

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

pour le react-router 2.5.2, la solution est si simple:

    //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 mai
la source
0

L'utilisation d'un composant d'itinéraire personnalisé est possible dans 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>
);

Quant au <MyRoute>code du composant, il devrait ressembler à:

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;

Pour plus de détails sur l'approche du composant d'itinéraire personnalisé, consultez mon article de blog sur le sujet: http://marmelab.com/blog/2016/09/20/custom-react-router-component.html

François Zaninotto
la source
0

c'est probablement la meilleure façon d'utiliser react-router-dom avec un gestionnaire de cookies

dans 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>
  )
}

et utilisez un 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
la source
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
la source
0

Utilisez la solution comme ci-dessous et cela fonctionne dans la version 3.2.5.

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

ou

<Route path="/foo">
  <Content
    lang="foo"
    meta={{
      description: lang_foo.description
    }}
  />
</Route>
illvart
la source