76

Here is the file that's causing me trouble:

var Routers = React.createClass({

  getInitialState: function(){
    return{
      userName: "",
      relatives: []
    }
  },

  userLoggedIn: function(userName, relatives){
    this.setState({
      userName: userName,
      relatives: relatives,
    })
  },

  render: function() {
    return (
      <Router history={browserHistory}>
        <Route path="/" userLoggedIn={this.userLoggedIn} component={LogIn}/>
        <Route path="feed" relatives={this.state.relatives} userName={this.state.userName} component={Feed}/>
      </Router>
    );
  }
});

I am trying to pass the new this.state.relatives and this.state.userName through the routes into my "feed"-component. But I'm getting this error message:

Warning: [react-router] You cannot change ; it will be ignored

I know why this happens, but don't know how else i'm supposed to pass the states to my "feed"-component. I've been trying to fix this problem for the past 5 hours and í'm getting quite desperate!

Please help! Thanks


SOLUTION:

The answers below were helpful and i thank the athors, but they were not the easiest way to do this. The best way to do it in my case turned out to be this: When you change routes you just attach a message to it like this:

browserHistory.push({pathname: '/pathname', state: {message: "hello, im a passed message!"}});

or if you do it through a link:

<Link 
    to={{ 
    pathname: '/pathname', 
    state: { message: 'hello, im a passed message!' } 
  }}/>

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

In the component you are trying to reach you can then access the variable like this for example:

  componentDidMount: function() {
    var recievedMessage = this.props.location.state.message
  },

I hope this helps! :)

peterh
  • 9,698
  • 15
  • 68
  • 87
Jonas Bergner
  • 1,205
  • 2
  • 13
  • 19
  • 1
    Check out this answer http://stackoverflow.com/a/29720488/444829 The `` there is from an old version,so you would continue to use the `component` prop, not the `handler` one that that answer uses. – Paul S Jan 04 '17 at 16:15
  • 1
    Will write up an answer with an explanation. – Paul S Jan 05 '17 at 15:46
  • nononono dont! i already found a solution! i deleted the comment i made immediately after i found the solution, but unfortunately u seem to have red it already. thanks a lot though :) – Jonas Bergner Jan 05 '17 at 15:50
  • @PaulS on second thought i'm always trying to learn and the solution i found appears to be only a semi-solution. do you mind explaining me how your mentioned solution would look like in my scenario? would be really great! :) – Jonas Bergner Jan 06 '17 at 00:35
  • 1
    Feel free to leave your updated solution as an answer and then mark your answer as the correct one. – dimiguel Dec 17 '17 at 03:36

4 Answers4

38

tl;dr your best bet is to use a store like redux or mobx when managing state that needs to be accessible throughout your application. Those libraries allow your components to connect to/observe the state and be kept up to date of any state changes.

What is a <Route>?

The reason that you cannot pass props through <Route> components is that they are not real components in the sense that they do not render anything. Instead, they are used to build a route configuration object.

That means that this:

<Router history={browserHistory}>
  <Route path='/' component={App}>
    <Route path='foo' component={Foo} />
  </Route>
</Router>

is equivalent to this:

<Router history={browserHistory} routes={{
  path: '/',
  component: App,
  childRoutes: [
    {
      path: 'foo',
      component: Foo
    }
  ]
}} />

The routes are only evaluated on the initial mount, which is why you cannot pass new props to them.

Static Props

If you have some static props that you want to pass to your store, you can create your own higher order component that will inject them into the store. Unfortunately, this only works for static props because, as stated above, the <Route>s are only evaluated once.

function withProps(Component, props) {
  return function(matchProps) {
    return <Component {...props} {...matchProps} />
  }
}

class MyApp extends React.Component {
  render() {
    return (
      <Router history={browserHistory}>
        <Route path='/' component={App}>
          <Route path='foo' component={withProps(Foo, { test: 'ing' })} />
        </Route>
      </Router>
    )
  }
}

Using location.state

location.state is a convenient way to pass state between components when you are navigating. It has one major downside, however, which is that the state only exists when navigating within your application. If a user follows a link to your website, there will be no state attached to the location.

Using A Store

So how do you pass data to your route's components? A common way is to use a store like redux or mobx. With redux, you can connect your component to the store using a higher order component. Then, when your route's component (which is really the HOC with your route component as its child) renders, it can grab up to date information from the store.

const Foo = (props) => (
  <div>{props.username}</div>
)

function mapStateToProps(state) {
  return {
    value: state.username
  };
}

export default connect(mapStateToProps)(Foo)

I am not particularly familiar with mobx, but from my understanding it can be even easier to setup. Using redux, mobx, or one of the other state management is a great way to pass state throughout your application.

Note: You can stop reading here. Below are plausible examples for passing state, but you should probably just use a store library.

Without A Store

What if you don't want to use a store? Are you out of luck? No, but you have to use an experimental feature of React: the context. In order to use the context, one of your parent components has to explicitly define a getChildContext method as well as a childContextTypes object. Any child component that wants to access these values through the context would then need to define a contextTypes object (similar to propTypes).

class MyApp extends React.Component {

  getChildContext() {
    return {
      username: this.state.username
    }
  }

}

MyApp.childContextTypes = {
  username: React.PropTypes.object
}

const Foo = (props, context) => (
  <div>{context.username}</div>
)

Foo.contextTypes = {
  username: React.PropTypes.object
}

You could even write your own higher order component that automatically injects the context values as props of your <Route> components. This would be something of a "poor man's store". You could get it to work, but most likely less efficiently and with more bugs than using one of the aforementioned store libraries.

What about React.cloneElement?

There is another way to provide props to a <Route>'s component, but it only works one level at a time. Essentially, when React Router is rendering components based on the current route, it creates an element for the most deeply nested matched <Route> first. It then passes that element as the children prop when creating an element for the next most deeply nested <Route>. That means that in the render method of the second component, you can use React.cloneElement to clone the existing children element and add additional props to it.

const Bar = (props) => (
  <div>These are my props: {JSON.stringify(props)}</div>
)

const Foo = (props) => (
  <div>
    This is my child: {
      props.children && React.cloneElement(props.children, { username: props.username })
    }
  </div>
)

This is of course tedious, especially if you were to need to pass this information through multiple levels of <Route> components. You would also need to manage your state within your base <Route> component (i.e. <Route path='/' component={Base}>) because you wouldn't have a way to inject the state from parent components of the <Router>.

Paul S
  • 29,223
  • 5
  • 39
  • 37
  • the question was changing the state of route itself – Khalid Azam Jan 06 '17 at 08:42
  • Please note that `React.PropTypes` has moved into a different package since React v15.5. Please use the "prop-types" library instead: https://www.npmjs.com/package/prop-types – Benny Neugebauer Oct 13 '17 at 10:28
  • So why is there a .state property in the LocationDescriptorObject interface? This is confusing - I cannot find anything in the docs... I mean I am using redux anyways but what is the intention of it? – Yannic Hamann Nov 30 '19 at 13:46
  • 1
    @YannicHamann location.state comes from the browser (the first argument to history.pushState/replaceState is serializable state to associate with location) and comes with caveats like it being null when you navigate directly to a page. I find that location.state is best for temporary data, like a URL to navigate to after logging in and "real" state is better to keep elsewhere. – Paul S Dec 07 '19 at 05:45
2

I know this might be late to answer this question, however for anybody that wonders about this in future, you can do this example way:

export default class Routes extends Component {
constructor(props) {
    super(props);
    this.state = { config: 'http://localhost' };
}
render() {
    return (
        <div>
            <BrowserRouter>
                <Switch>
                    <Route path="/" exact component={App} />
                    <Route path="/lectures" exact render={() => <Lectures config={this.state.config} />} />
                </Switch>
            </BrowserRouter>
        </div>
    );
}

}

This way you can reach config props inside Lecture component. This is a little walk around the issue but for a start its a nice touche! :)

Damian Busz
  • 225
  • 2
  • 12
0

You can not change the state of the React-router once the router component is mounted. You can write your own HTML5 route component and listen for the url changes.

class MyRouter extend React.component {
   constructor(props,context){
   super(props,context);
   this._registerHashEvent = this._registerHashEvent.bind(this)

 } 

 _registerHashEvent() {
    window.addEventListener("hashchange", this._hashHandler.bind(this), false);
  }
  ...
  ...

render(){
  return ( <div> <RouteComponent /> </div>)
}
theUtherSide
  • 2,800
  • 3
  • 28
  • 34
Khalid Azam
  • 1,375
  • 15
  • 16
  • 1
    I don't think this is really answering the original question. How does this provide the ability to tie a route to state? – theUtherSide May 01 '18 at 20:20
0

For those like me,

You can also normally render this:

import {
  BrowserRouter as Router, 
  Router, 
  Link, 
  Switch 
} from 'react-router-dom'

<Router>
  <Switch>
   <Link to='profile'>Profile</Link>

   <Route path='profile'>
     <Profile data={this.state.username} />
   </Route>
   <Route component={PageNotFound} />
  </Switch>
</Router>

This worked for me!