react-router - passa adereços ao componente manipulador

315

Eu tenho a seguinte estrutura para o meu aplicativo React.js usando o 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);
});

Eu quero passar algumas propriedades para o Commentscomponente.

(normalmente eu faria isso como <Comments myprop="value" />)

Qual é a maneira mais fácil e correta de fazer isso com o React Router?

Kosmetika
fonte
O problema aqui, e em casos semelhantes, especialmente com os frameworks ou bibliotecas escritos em alguns idiomas, uma certa falta de meios de combinação (MoC). As primitivas parecem ok no React, elas são muito boas, definindo componentes com primitivas, nos elementos React e no componente MoC , que também parece bem no React. Mas os meios de combinação são incompletos. É preciso passar os adereços para um componente enquanto combina um componente para outro, não importa se colocar um componente dentro de outro componente como filho dele ou passar um componente como adereços para outro.
Selçuk
Com alguma sintaxe como <ComponentA x={<ComponentB y={<ComponentC z={} />} />} /> OR <ComponentA x={ComponentB(ComponentC()) } /> Caso contrário, esses problemas de combinações de abstrações ocorrerão e precisarão de soluções menos que ótimas e indiretas chamadas soluções alternativas, como empacotamento etc., etc. As abstrações devem ser cidadãos de primeira classe como primitivos, qualquer que seja a percepção de primeira classe.
Selçuk 03/03

Respostas:

152

ATUALIZAR

Desde a nova versão, é possível transmitir adereços diretamente através do Routecomponente, sem usar um Wrapper. Por exemplo, usando 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} />} />

Exemplo de Codesandbox


VERSÃO ANTIGA

Minha maneira preferida é envolver o Comments componente e passar o wrapper como um manipulador de rota.

Este é o seu exemplo com as alterações aplicadas:

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
fonte
58
Estou com o mesmo problema, mas essa solução não é detalhada rapidamente?
usar o seguinte código
8
Concordo com captDaylight, torna-se detalhado. Prefere uma maneira melhor de lidar com isso!
Mattias Hallström
6
@mattiashallstrom IMO, a melhor maneira na versão 1.0 é simplesmente adicionar a propriedade à rota. Veja a resposta de Thomas E.
K00k
28
você pode adicionar uma sintaxe componente sem estado (apenas lambda), há também, é bastante curta<Route path="comments" component={() => (<Comments myProp="value" />)}/>
Ciantic
33
Eu nunca vou concordar em criar componentes adicionais apenas para passar propriedades sendo a "maneira preferida". É detalhado, complicado, propenso a erros e simplesmente errado em todos os sentidos imagináveis. Pode ser a única maneira que o roteador de reação permite, mas chamá-lo de "preferido" é um exagero. Preferido por quem?
Szczepan Hołyszewski
260

Se você preferir não escrever wrappers, acho que você poderia fazer isso:

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
fonte
11
Esta é uma resposta correta. No reat-router 1.0, você pode obter routeum objeto comum em seu componente. Aqui está a resposta de emissão github: github.com/rackt/react-router/issues/615#issuecomment-100432086
ycdesu
4
Esta é a resposta simples que eu estava procurando. As outras técnicas funcionam, mas exigem 10x a quantidade de código. Funciona bem para v1.0.x. A única desvantagem que vejo é se você pretende usar o mesmo componente com e sem o contêiner do roteador. Mas para mim, todos os meus componentes de nível superior mapearam 1 para 1 com rotas.
killthrush
12
Ótimo, obrigado! Se alguém está se perguntando, a propriedade foo estaria disponível em seu componente como: this.props.route.foo
k00k
8
Este pode ser definido como uma resposta correta para evitar a confusão?
Archibald
2
Não! Veja a resposta de Rajesh Naroth para a solução real :) #
Alex Alex
114

Copiando dos comentários do ciantic na resposta aceita:

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

Esta é a solução mais elegante na minha opinião. Funciona. Me ajudou.

Rajesh Naroth
fonte
É basicamente o mesmo que a resposta do wrapper acima, mas é muito menos prolixo. Cara, porém, essa sintaxe é péssima. Tente jogar em um_ref
EdH 3/16/16
11
É como um invólucro anônimo, portanto, todos os adereços injetados (por exemplo, localização) são perdidos. Um tem que passar manualmente adereços como component={(props) => (<Comments myProp="value" location={ props.location } />)}, mas tudo fica confuso novamente
Yuji
1
@JacobThomason, não renderizamos novamente a configuração do roteador de reação, por isso é improvável que seja uma penalidade de desempenho.
Sebastien Lorber
9
A partir do React-Router 4, se você fornecer uma função em linha, receberá muitas remontagens indesejadas. Para renderização embutida, use o suporte de renderização. Link para a documentação
Daniel Reina
1
@yuji Para não torná-lo um muito confuso pode fazer: component={(props) => (<Comments {...props} myProp="value" />)}para manter os adereços injetados
apelsinapa
57

Esta é a solução de Rajesh , sem o inconveniente comentado por yuji , e atualizada para o React Router 4.

O código seria assim:

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

Note que eu uso em rendervez de component. O motivo é evitar remontagens indesejadas . Também passo o propsmétodo a ele e uso os mesmos adereços no componente Comentários com o operador de propagação de objetos (proposta ES7).

Daniel Reina
fonte
Realmente ótimo com a solução de montagem indesejada também! 1
GLindqvist
44

Apenas um acompanhamento da resposta de ColCh. É muito fácil abstrair o agrupamento de um 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})}/>

Ainda não testei esta solução, portanto, qualquer feedback é importante.

É importante observar que, com esse método, quaisquer objetos enviados pelo roteador (como parâmetros) são substituídos / removidos.

sigmus
fonte
1
Bob, você conhece os fechamentos? stackoverflow.com/questions/111102/…
sigmus
3
E se você também precisar da consulta e parâmetros do roteador, algo assim funcionará: return React.createElement(Component, _.assign({}, this.props, props));(Este usa _.assign para compor o objeto combinado ... outros métodos estão disponíveis, é claro).
Malcolm Dwyer
2
Você também pode passar as crianças. var wrapComponent = function (Componente, props) {return React.createClass ({render: function () {return React.createElement (Componente, props, this.props.children);}}); };
Julio Rodrigues
1
Este é um componente de ordem superior para aqueles que não estão familiarizados com eles facebook.github.io/react/docs/higher-order-components.html
icc97
1
Nota: agora é para uma versão antiga do React Router. V4 atual tem render, componente childrenmétodos para Route. Observe que, como a resposta do @dgrcode aponta, você deve usar em rendervez decomponent
icc97 27/17
31

Você pode passar props passando-os para <RouteHandler>(na v0.13.x) ou o próprio componente Route na v1.0;

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

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

(a partir do guia de atualização em https://github.com/rackt/react-router/releases/tag/v1.0.0 )

Todos os manipuladores de crianças receberão o mesmo conjunto de acessórios - isso pode ser útil ou não, dependendo da circunstância.

cachvico
fonte
1
É realmente intrigante ver React.cloneElementpassar vários elementos, mas a assinatura da função parece levar apenas um elemento de reação. Acho que esse trecho pode ser mais fácil de entender.
manu
2
Essa é claramente a melhor resposta no alvo com os documentos, mas também concordo com o manu de que poderia ser escrito para mostrar melhor o uso. Código mais específico para a questão seria parecido com: React.cloneElement(this.props.children, {myprop: "value"})ou React.cloneElement(this.props.children, {myprop: this.props.myprop})etc.
juanitogan
Esta resposta vence. Também é muito mais claro o que está acontecendo no meio das coisas que o Roteador faz por você. Como alguém que lê o código, se eu souber que Comentários está dentro do Índice, examinarei o Índice para ver quais objetos são enviados para Comentários. Se eu souber que Comments é um manipulador de roteador, examinarei a configuração do roteador e descobrirei que os comentários dos pais do Index ainda continuarão lá.
mjohnsonengr
24

Usando o ES6, você pode simplesmente colocar os wrappers de componentes em linha:

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

Se você precisa passar crianças:

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

usuario
fonte
Isso é legal, mas não passa pelas crianças, caso haja alguma.
Zpr 14/08/19
@zpr Adicionei um exemplo para props.children
Nick
2
Como a resposta do @dgrcode aponta, você deve usar em rendervez decomponent
icc97
23

React-router v4 alpha

agora há uma nova maneira de fazer isso, embora muito parecida com o 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} />} />

PS Isso funciona apenas na versão alfa e foi removido após o lançamento da v4 alpha. Na v4 mais recente, é mais uma vez, com o caminho e adereços exatos.

react-lego, um aplicativo de exemplo contém código que faz exatamente isso em routes.js em sua ramificação react-router-4

peter.mouland
fonte
21

Aqui está a solução mais limpa que eu encontrei (React Router v4):

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

MyComponentainda tem props.matche props.location, e tem props.foo === "lol".

cgenco
fonte
13

Envolva-o com um componente de função sem estado:

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

Você também pode usar o RouteHandler mixin para evitar o componente wrapper e passar mais facilmente o estado do pai como props:

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
fonte
1
É exposto publicamente na construção global do caramanchão como ReactRouter.RouteHandlerMixin, então acho que não.
julho
Isso também permite animar transições usando TransitionGroup e CSSTransitionGroup, que não consegui trabalhar usando o método wrapper.
julho
2
É estranho que não haja menção sobre isso nos documentos oficiais.
Kosmetika
Os mixins não são mais recomendados pelos documentos do React: facebook.github.io/react/blog/2016/07/13/…
icc97
12

Você pode passar adereços através do <RouterHandler/>seguinte:

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

A desvantagem disso é que você está passando adereços indiscriminadamente. Portanto, Commentspode acabar recebendo adereços que realmente são destinados a um componente diferente, dependendo da configuração de suas rotas. Não é um grande negócio, pois propsé imutável, mas isso pode ser problemático se dois componentes diferentes estiverem esperando um adereço nomeado, foomas com valores diferentes.

Meistro
fonte
1
O que os três pontos / pontos fazem ou significam neste código:{...props}
Giant Elk
2
Eu acho que o Flux evitaria a necessidade de enviar o estado do aplicativo pai para uma rota. Eu consegui o código acima funcionando, mas não é explícito, portanto o majic oculto é feio, não é fácil rastrear e rastrear o que está acontecendo.
Giant Elk
2
Explique a explicação do operador . Não é explícito, mas não é a pior coisa, pois você está passando por objetos imutáveis.
Meistro 04/04
Aqui estão os documentos do ReactJS sobre o operador de propagação: facebook.github.io/react/docs/jsx-spread.html e também facebook.github.io/react/docs/transferring-props.html
Giant Elk
isso funcionou para mim, mas a sintaxe correta é {... this.props}
Win:
10

Nas versões 1.0 e 2.0, você pode usar createElementprop of Routerpara especificar exatamente como criar seu elemento de destino. Fonte da documentação

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

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

Você também pode combinar as funções es6 e stateless para obter um resultado muito mais limpo:

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
fonte
Não sei exatamente como isso funciona - mas parece errado. Você está usando this.propsuma função, que tenho certeza que não funcionará. Se você estiver usando funções puras em vez de estender o React.Componentprops
arquivo
6

Solução React Router v 4

Eu me deparei com essa pergunta hoje cedo, e aqui está o padrão que eu uso. Espero que isso seja útil para quem procura uma solução mais atual.

Não tenho certeza se essa é a melhor solução, mas esse é o meu padrão atual para isso. Normalmente, tenho um diretório Core em que mantenho meus componentes comumente usados ​​com suas configurações relevantes (carregadores, modais etc.) e incluo um arquivo 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

Em seguida, no arquivo em questão, farei o seguinte:

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

Você notará que eu importo a exportação padrão do meu componente como humilde caso de camelo, o que me permite nomear o novo componente com reconhecimento de local no CamelCase para que eu possa usá-lo normalmente. Além da linha de importação adicional e da linha de atribuição, o componente se comporta conforme o esperado e recebe todos os seus adereços normalmente, com a adição de todos os adereços de rota. Assim, posso redirecionar com prazer dos métodos do ciclo de vida do componente com this.props.history.push (), verificar o local etc.

Espero que isto ajude!

Chris
fonte
4

Para o roteador de reação 2.x.

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

e nas suas rotas ...

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

certifique-se a 3ª param é um objeto como: { checked: false }.

holyxiaoxin
fonte
1

O problema com o React Router é que ele processa seus componentes e impede que você passe em adereços. O roteador de navegação , por outro lado, permite renderizar seus próprios componentes. Isso significa que você não precisa pular nenhum arco para passar em adereços como o código a seguir e o programa JsFiddle que o acompanha .

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

Use o componente com ou sem roteador com base na resposta 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 você pode fazer assim:

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

para o react-router 2.5.2, a solução é tão 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 maio
fonte
0

Usando um componente de rota personalizado , isso é possível no 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>
);

Quanto ao <MyRoute>código do componente, deve 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 obter mais detalhes sobre a abordagem do componente de rota personalizada, consulte minha postagem no blog sobre o assunto: http://marmelab.com/blog/2016/09/20/custom-react-router-component.html

François Zaninotto
fonte
0

esta é provavelmente a melhor maneira de usar o react-router-dom com um manipulador de cookies

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

e use um cookieCheck

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

Use a solução como acima e isso funciona na v3.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
fonte