I ran into an issue with HotModuleReloading, using ReactRouter for the first time. The browser console would display the correct change updates, but the window itself would not update.
From the official docs:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
I read that as render reduces the amount of unnecessary re-renders, here are their docs:
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop receives all the same route props as the component render prop.
I had been using the render method like so:
const App = () => {
return (
<Switch>
<Route exact path="/" render={() => <Home />} />
</Switch>
);
};
I tried removing my Redux <Provider> content, but no changes. So I swapped out render for component like so and it works fine:
const App = () => {
return (
<Switch>
<Route exact path="/" component={Home} />
</Switch>
);
};
So, why is this?? What am I missing?
When you use component the Route component passes certain props to the rendered component - such as location, history, match etc.
When you use render you're rendering the component in JSX like <Home />. This doesn't have any props passed to it.
If for whatever reason, you'd need to use render over component, you should pass the same props as component does:
const App = () => {
return (
<Switch>
<Route exact path="/" render={(props) => <Home ...props />} />
</Switch>
);
};
This will make sure Home gets the props it needs to deal with Router stuff.
Hope this helps you get to the bottom of it!
Related
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.
I’m using the last release of React Router and I want to know what is the best solution to show different component that are nested in a parent component.
I’ll try to explain myself better.
I have a route R with path /r1.
This route loads component A.
Component A has inside others 3 components B, C and D that I should show in the same page with component A only when the user press a specific button in component A.
So I want to be able to add a description in route R to manage this. For example to show component B the router could be /r1/b.
Now I did this with a state variable inside component A but I think should be better if I can use some React Router property.
Thanks
You can create nested routes component, and it will manage nested routes.
export default function NestedRoutes() {
return (
<Switch>
<Redirect exact from={"/r1"} to={`/r1/A`} />
<Route path={`/r1/A`}>
<ComponentA />
</Route>
<Route path={`/r1/B`}>
<ComponentB />
</Route>
// Or to some not found component
<Redirect to="/r1/A" />
</Switch>
);
}
I'm using Switch with my route entries. The problem was that I didn't know how to render a component that I wanted to pass by props to another component.
I added a prop component to my parent component A and in my route I wrote something like this:
<Route path="/r1/hub/A" render={() => <A /> //this render only A
<Route path="/r1/hub/A/B" render={() => <A component={B} /> //this render A with B
In component A I used React.createElement to render component B with others properties that component A has to inject.
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.
I am trying to pass props to my components through the accepted way from various sources like this comment
This is my current code
<Router>
...
<Route path="/:id" exact component={() => <GymMain id={params.id} appointmentTypes={appointmentTypeList} />} />
<Route path={`/:id/:name`} component={(props) => {
const { params } = props.match;
const aType = appointmentTypeList.find(at => at.uri === params.name);
return <AppointmentType id={params.id} appointmentType={aType} />
}} />
...
</Router>
However this causes the components to mount twice, once when you navigate into it and then when you navigate away from it (while navigating away old props are passed). This is happening because I am decorating my original component with an anonymous one as explained in this answer .
My Question is how can I prepare the props for child components based on the route params and then pass it on to the routed component. Thanks!
There is a small difference between using component prop vs render prop to render a functional component.
As per the docs:
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from
the given component. That means if you provide an inline function to
the component prop, you would create a new component every render.
This results in the existing component unmounting and the new component mounting instead of just updating the existing component.
When using an inline function for inline rendering, use the render or
the children prop (below).
Hence you see the above behaviour in your case. Change your code to use render prop and it would work fine
<Router>
...
<Route path="/:id" exact render={() => <GymMain id={params.id} appointmentTypes={appointmentTypeList} />} />
<Route path={`/:id/:name`} render={(props) => {
const { params } = props.match;
const aType = appointmentTypeList.find(at => at.uri === params.name);
return <AppointmentType id={params.id} appointmentType={aType} />
}} />
...
</Router>
This is not the best way for doing so.
you can use UNSAFE_componentWillUpdate() lifecylcle method to check incoming props.
UNSAFE_componentWillUpdate(nextProps, nextState){
// check for your condition for updating component
if (this.porps !== nextProps) {
this.forceUpdate()
}
else {
return
}
}
const Home = () => <div>Home</div>
const App = () => {
const someVariable = true;
return (
<Switch>
{/* these are good */}
<Route exact path='/' component={Home} />
<Route
path='/about'
render={(props) => <About {...props} />}
/>
</Switch>
)
}
const About = (props) => {
return (
<div>
About
</div>
)
}
In the code sample , at
<Route
path='/about'
render={(props) => <About {...props} />}
/>
when react encounters the render prop of the Route component which is part of react-router, what does it pass a props?
Given the documentation at https://reactjs.org/docs/render-props.html ,
a render prop is a function prop that a component uses to know what to render,
is the value passed a props buried inside the declaration of Route in react-router
The props are passed to the render prop method by the Route component. You can see this in the React Router source code. The props passed by the Route component have match, location, history, staticContext. If you want to use props from the parent component, where you are defining the render props method then you can omit the props argument.
render={() => <About {...props} />}
Then you would get the props from the component that contains the Route.
The example you have provided doesn't make much sense since that replicates the behaviour that you get by just using the 'component' prop on the Route.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js#L120
We use Route with render props as,
<Route path = "/about" component={About} />
OR,
<Route path = "/about" render= { (props) => <About {...props} } />
The second one is different from the first one in the sense that in the second case, the About component has access to the props coming through the Route.
Say, for instance,
there is a Profile component,
<Route path="/admin/profile"
render={ props => (
<Profile tabs= {"valuePassed"} {...props} />
)}
/>
Now in Profile component, we can access all the props,
this.props.tabs give "valuePasses" in class-based component while props.tabs is used for functional component.
Hope this helps.
You get react router default props while passing props in render method just like if use component instead of using render props which implicitly get all these props match, location, history and staticContext. and you need to provide props as an argument otherwise it render method won't pass props down to the children because it will consider it undefined.
Here is working example for render props in react router:
https://codesandbox.io/s/72k8xz669j