How do I pass state through React_router? - reactjs

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

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

I know this is a late answer, but you can do it this 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 the Lecture component.
This is a little walk around the issue but it is a nice start.

Just a heads up that if you're using a query string you need to add search.
For example:
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: true
}
}
It took like 20 minutes to figure out why my route was not being rendered 😅

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

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!

Related

What is the simplest way to pass state while using React Router?

What is the simplest way to pass state while using React Router? My Navi component below is reflecting user being null, as opposed to user being "KungLoad". Thanks.
class App extends Component{
constructor() {
super();
this.state = {user: "KungLoad"};
}
render () {
return(
<div>
<Router>
<Route exact path="/" state component = {Navi} />
</Router>
The simplest way is that you can pass the state as props and then use it in the specified component. For your case, you have to use render instead of component for passing the state as props.
<Route exact path="/" render={() => <Navi user={this.state.user} />} />
This will work but I would recommend to you that the Context API concept of reactJS would be best suited here. You can pass the state or props to all the component using the data provider and all the components will consume the state or props that are being provided by the parent component. . https://reactjs.org/docs/context.html
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Yiu can pass your state as props to your Navi component like this: <Route exact path="/" render={() => <Navi user={this.state.user} />} />
The other answers are correct, you should pass state down to children components via props. I am adding my answer to highlight one additional way that the Route component can be used. The code looks cleaner and is easier to read if you simply add children to a Route component, rather than use the render or component prop.
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: "KungLoad"
};
}
render() {
return (
<div>
<Router>
<Route exact path="/">
<Navi user={this.state.user} />
</Route>
</Router>
</div>
);
}
}
After making the state and assigning value
this.state = {user: "KungLoad"};
Passing the state value to the router is done like this.
<Router>
<Route exact path="/" render={()=> (<Navi user={this.state.user}/>)} />
</Router>
Or if you want to user is not logged in use a redirect
<Route exact path="/signin" render={()=> (<Redirect to='/signin'/>)}/>

React router: How to update component outside route on param change?

When a url param change, I need to update two components, but one of them is outside the route with the param. The routes in App.js are like this:
<BrowserRouter>
<div>
<Route exact path="/" render={ (props) =>
<Home products={this.state.products} }
/>
<Route path="/products/:product" render={ (props) =>
<Product {...props} /> }
/>
<Route path="/" render={ props =>
<ProductHistory {...props}/> }
/>
</div>
</BrowserRouter>
The ProductHistory which is always visible has links pointing to products, like:
<Link to={`/products/${product.product_id}`}> {product.name}</Link>
When following such a link, the Product component is updated using ComponentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.match.params.product !== this.props.match.params.product){
But how do I update the ProductHistory component at the same time when the product param change? Since it isn't within the /products/:product route, checking this.props.match.params.product in ProductHistory's componentWillReceiveProps results in undefined.
(edit - and withRouter doesn't help, since it already is within a route, but a different one: "/")
In componentWillReceiveProps I could use location.pathname to check that the path begins with "/product", and I could find the param by substr(path.lastIndexOf('/') + 1.
Edit: But I also have to compare the current id param with the next product id param to avoid unnecessary updates. But when clicking the link, the url have already changed when componentWillReceiveProps fires so location.pathname and nextProps.location.pathname always match, so it updates unnecessarily (repeated api calls).
So I would have to find a different solution - rearrange the routing in some way? The idea is that ProductHistory should always be visible though.
You can render the Route simply like this:
<BrowserRouter>
<div>
<Switch>
<Route exact path="/" render={ (props) =>
<Home products={this.state.products} }
/>
<Route path="/products/:product" render={ (props) =>
<Product {...props} /> }
/>
</Switch>
<ProductHistory />
</div>
</BrowserRouter>
And then in the ProductHistory class you use the withRouter HOC
You can get access to the history object's properties and the closest
Route's match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
example:
class ProductHistory extends Component { ... }
export default withRouter(ProductHistory);
or using decorators
#withRouter
export default class ProductHistory extends Component { ... }
With this you will be able to access match, location and history through props like this:
this.props.match
this.props.location
this.props.history
For anyone stumbling across this, there is a new solution afforded by hooks, in the form of useRouteMatch in react-router-dom.
You can lay your code out like João Cunha's example, where ProductHistory is not wrapped within a Route. If the ProductHistory is anywhere else but inside the Route for products, all the normal routing information will seemingly give the wrong answer (I think this might have been where the problems with withRouter arose in the replies to João's solution), but that's because it's not aware of the product route path spec. A little differently from most MVC routers, React-router-dom won't be calculating the route that matched up front, it will test the path you're on with every Route path spec and generate the specific route matching info for components under that Route.
So, think of it in this way: within the ProductHistory component, you use useRouteMatch to test whether the route matches a path spec from which you can extract the params you require. E.g.
import { useRouteMatch } from 'react-router-dom';
const ProductHistory = () => {
const { params: { product } } = useRouteMatch("/products/:product");
return <ProductList currentProduct={product || null} />;
};
This would allow you to test and match against multiple URLs that might apply to products, which makes for a very flexible solution!

React Router causing component remount on Firebase update

I have an App component which, using react-router, holds a few components in two routes. I also have a Firebase data store which I want to bind to the state of App (using rebase) so I can pass it down to any component I wish as a prop. This is my App class:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: {}
};
}
componentDidMount () {
rebase.bindToState('items', {
context: this,
state: 'items'
})
}
render() {
return (
<Router>
<div className='container'>
<div className="header">
<h3>Header</h3>
<Nav/>
</div>
<Switch>
<Route exact path='/' component={() => <Home items={this.state.items} rebase={rebase} />} />
<Route render={function () {
return <p>Not Found</p>
}} />
</Switch>
</div>
</Router>
)
}
}
Now, when I load my page I get two mounts of the Home component. This in itself is not great. However, I have several actions in the Home component that use rebase to modify/read from Firebase. As a callback of these actions they also change the Home component's state. The problem is, whenever I do a Firebase call, it remounts the Home component and any state I have is lost.
If I remove the Router wrappers from the Home component, and render it purely as render( <Home items={this.state.items} rebase={rebase} /> ), my app works perfectly as intended. I don't know why wrapping it in Router stuff makes it not work. I thought it was because I had additional URL parameters that also changed when I call firebase updates (e.g. /?p=sgergwc4), but I have a button that changes that parameter without a firebase update and it doesn't cause any problems (i.e. doesn't cause a remount). So what's up with the Router?
Turns out the answer is simple; instead of component={}, I should use render={}. Fixes everything. It was in the docs too.

Passing props to component inside router

I want to use URL paths for my app. Currently I just render a Main component in app.js:
render() {
return (
<Main
isLoggedIn={this.state.isLoggedIn}
user={this.state.user}
... />
)
}
(The props are a bunch of variables and functions)
I tried using react-router but I don't understand how to send props to the child components.
<Router history={browserHistory} >
<Route path='/' component={Main}
... where to send props? />
<Route path='join' component={Join}
... props />
</Router>
The problem is that I need some state variables of my App component (in app.js) to be available in both Main and Join. How do I achieve that with a router?
App structure:
App
- Join
- Main
- ...
It depends how you are managing your state. if you use the Redux, you don't need to pass props in router, you can simple access redux in any component using connect, However if you don't use react then you can use the Redux router's hierarchy functionality
Assume you have a route something like this
<route path="/student" component={studentHome}>
<route path="/class" component={classHome} />
</route>
when you access the path
/student/class
React router will load the classHome component as children of StudentHome and you can simple pass props to classHome component.
you student class Render method will be something like
render(){
const childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
doSomething: this.doSomething
})
);
return <div> some student component functionality
<div>
{childrenWithProps}
</div>
</div>
}
more details about passing props to children:
How to pass props to {this.props.children}
You can use standard URL parameters. For example, to pass an ID:
<Route path='join/:id' component={Join}/>
You'll need to use browserHistory:
browserHistory.push(`/join/${id}`);
Then use this.props.params.id on Join.
Here's a more in-depth explanations https://stackoverflow.com/a/32901866/48869

React Dynamic menu items passed down via router

Another react question here, I have a solution for my problem but to me it doesn't seem very "React" so I was hoping for another solution.
I'm using react router so the bottom of my app.js(entry point) is:
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={Layout}>
<IndexRoute component={Login} ></IndexRoute>
<Route path="searches" component={searches}></Route>
<Route path="notifications" component={notifications}></Route>
</Route>
</Router>
, app);
Now as you can see my overarching component is Layout so, in my mind I want to configure my (reusable) components, for example I want my Layout to pass the title of the menu items to the header component, and then if I'm for example loading a search then I might want to pass functions etc to the search component to hook into it's functionality, so I have the following in layout:
export default class Layout extends React.Component {
constructor() {
super();
}
render() {
const containerStyle = {
paddingRight: '5px'
}
// Figure out which props we want based on location.pathname
const {pathname} = this.props.location;
switch(pathname){
case "/searches":
// So now I need to add some props, functions or anything else to this component
theProps = {someProp: "value"}
const theComponent = React.cloneElement(this.props.children, {theProps})
break;
}
return (
< div style={containerStyle} class="container">
< Header menuItems={this.getMenuItemsForScreen()}/ >
{ theComponent}
< Footer / >
< /div>
);
}
}
So basically in order to pass props from my overarching Layout I have to clone the component and then give it some more props?? It just feels a bit dirty but I can't seem to find a way of embedding this type of logic otherwise?
Thanks
Marc
I think the great thing about these routing components is that they save you from those ugly switches in your components.
I'm not sure which kind of props you want to send to the searches component. In your question is not clear what is the actual problem you are trying to solve instead of using one of the standard approaches in the react-router documentation.
I recommend considering these alternatives:
Refactor your searches component to avoid receiving any props. Try to have each route to have a component that doesn't receive any props. So you move that code that define the props (theProps = {someProp: "value"}) inside the searches component. Or if you need the searches component to be reused with those props and other props in another time, then make a new parent component that defines those props and calls the searches component then. But if those props are to complex and dependent on your app state then maybe you can consider using flux, redux or another state container, and get those from the app state.
If you really need the routing parameters, then make sure the props can be serialized so they can be part of the URL. Check the message route in the code below (copied from RouteConfiguration sample):
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, Link } from 'react-router'
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{this.props.children}
</div>
)
}
})
const About = React.createClass({
render() {
return <h3>About</h3>
}
})
const Inbox = React.createClass({
render() {
return (
<div>
<h2>Inbox</h2>
{this.props.children || "Welcome to your Inbox"}
</div>
)
}
})
const Message = React.createClass({
render() {
return <h3>Message {this.props.params.id}</h3>
}
})
render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
In this case your code will have <a href={"/inbox/message/"+id} ...> somewhere in your code and those will provide the props by setting the id parameter in this case.
you use functional component in child component with this code :
<Route path="/:id" component={Child} />
function Child({ match }) {
return (
<div>
<h2>ID:{match.params.id}</h2>
</div>
);
}

Resources