React Router V4 - Can't get URL params in props - reactjs

I'm trying to create simple React-Redux-Router V4 app. The route to one of my components makes use of the url param. In my Route components, I've been passing in the render prop, which is an anonymous function that returns a component. Like so:
<Route key="post" path="/post/:id" render={() => <Post/>} />
I use this prop as opposed to the component prop:
<Route key="post" path="/post/:id" component={Post} />
Because I'm not able to props into the rendered component using the component prop. The issue is, with the component prop, you can make use of props.match.params.name, which will contain the exact param I need. This is not available when using the render component.
I can use window.location.href but it feels dirty.
Thanks!

The match, location, and history props are passed into your render function.
Example:
<Route key="post" path="/post/:id" render={({ match }) => <Post name={match.params.name}/>} />
Documentation: https://reacttraining.com/react-router/web/api/Route/Route-props

You need to import this first
import { withRouter } from 'react-router-dom';
After that at the end export the component like this. 'User' is a class component name
export default (withRouter(User));
Now, If you will print the value you will get a response.
constructor(props) {
super(props);
console.log('Props', this.props);
}
Happy Coding

Related

Non-matching routes are rendered with React Router

I'm using React Router with TypeScript. Here's two routes, namely / and /pages/id.
class MyComponent extends React.Component
{
render()
{
return <BrowserRouter>
<div>
<Route exact path='/' children={
() =>
<TopPage />} />
<Route exact path='/pages/:id' children={
(props: RouteComponentProps<{id: string}>) =>{
console.log(props)
return <IndividualPage match={props.match} />}} />
</div>
</BrowserRouter>
}
}
I expect, since my Routes are exact:
when I access /, only TopPage component is rendered
when I access /pages/1, only IndividualPage component is rendered
when I access /no-pages, nothing is rendered
However, I observed:
when I access /, or /no-pages, IndividualPage component is rendered with match=null (causing Cannot read property 'params' of null error)
when I access /pages/1, both TopPage and IndividualPage components are rendered
What am I doing wrong?
There are four different ways to set the render component for a Route, some of which behave slightly differently than others. Setting the children prop to a render function is one of those ways, but it's not the one that you want based on the behavior you expect. Unlike the other methods, the contents of a children function are always rendered even if the current page is not a match. So you will have both components rendering on every page. Per the docs:
Sometimes you need to render whether the path matches the location or not. In these cases, you can use the function children prop. It works exactly like render except that it gets called whether there is a match or not.
Instead you can use the render prop or the component prop. These will only render if the route is a match.
The default behavior of the Router is to render all Routes which match, not just the first. In practice, this is a non-issue with your example because you have two exact routes so there is no possibility that both will match. But as a habit I would recommend putting your Route components inside of a Switch component which will only render the first matching Route.
class MyComponent extends React.Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={TopPage} />
<Route exact path="/pages/:id" component={IndividualPage} />
</Switch>
</BrowserRouter>
);
}
}
No typescript annotations are needed here because the Route component knows that RouteComponentProps will be passed to the component.

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

Difference between passing component to Route as prop and wrapping component in render function

What is the difference between routing to a component like this:
<Route path="coolPath" component={MyComponent} />
or
<Route path="coolPath" render={props => <MyComponent {...props} customProp="s" } />
To this:
<Route path"=coolPath">
<MyComponent />
</Route>
or
<Route path"=coolPath">
<MyComponent cusomProps="cp"/>
</Route>
first you should read through this site:
https://reacttraining.com/react-router/web/api/Route
But to explain, there's three things going on here, the first two are examples of routing with previous version of react-router (before v5) and the third is react-router (v5 - current) recommended approach.
1. Route with component
<Route path="/coolPath" component={MyComponent} />
This type of route renders the single component passed to the prop. If an inline function is passed to the Route's component prop, it will unmount and remount the component on every render via the use of React.createElement. This can be inefficient, and passing custom props via this method is only possible via an inline function. React Router's authors recommend using the render prop as opposed to the component prop for handling inline functions, as shown below.
2. Route with render
<Route path="/coolPath" render={props => <MyComponent {...props} customProp="s" } />
Instead of having a new React element created for you using the component prop with an inline function, this route type passes in a function to be called when the location matches and does not unmount a component and remount a brand new one during rerender. It's also much easier to pass custom props via this method.
3. Route with children as components
<Route path="/coolPath">
<MyComponent customProp="s" />
</Route>
This is currently the recommended approach to routing, the child components will be rendered when the path is matched by the router. It's also very easy to pass custom props with this method.
Keep in mind there is a fourth type, which is:
4. Route with children as function
From reacttraining.com:
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Link,
Route
} from "react-router-dom";
function ListItemLink({ to, ...rest }) {
return (
<Route
path={to}
children={({ match }) => (
<li className={match ? "active" : ""}>
<Link to={to} {...rest} />
</li>
)}
/>
);
}
ReactDOM.render(
<Router>
<ul>
<ListItemLink to="/somewhere" />
<ListItemLink to="/somewhere-else" />
</ul>
</Router>,
node
);
Sometimes you need to render whether the path matches the location or not. In these cases, you can use the function children prop. It works exactly like render except that it gets called whether there is a match or not.

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!

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

Resources