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
}
}
Related
I'm using "react-router-dom": "5.2.0". According to this stack post and blog to pass props to a component from route all I need to do is simply pass it like so:
{caseManagement && <Route path={`${SPA_PREFIX}/cases/:caseToken`}> <CaseView sarReport={sarReport} /> </Route>}
However, when I do this my complier doesn't complain, but in my browser I get the following error.
CaseView.jsx:48 Uncaught TypeError: Cannot read properties of undefined (reading 'params') at CaseView (CaseView.jsx:48:1)
If I change the code to the following it works as expected.
{caseManagement && <Route path={`${SPA_PREFIX}/cases/:caseToken`} component={CaseView} />}
Of course in this implementation I'm not passing any props to it. How can I pass props to my component?
Below is the relative code of the component I'm passing props to
const CaseView = ({
match: { params: { caseToken } },
}, sarReport ) => {
...
Issues
<Route path={`${SPA_PREFIX}/cases/:caseToken`}>
<CaseView sarReport={sarReport} />
</Route>
When rendering CaseView as a child you can pass any props you like, but the route props, specifically match, are not passed and the error occurs when accessing props.match.params.
<Route path={`${SPA_PREFIX}/cases/:caseToken`} component={CaseView} />
When rendering on the component prop, the route props are passed along, so props.match.params works, but now you can't pass the additional sarReport prop.
Solution
In react-router-dom v5 to pass along the route props (i.e. history, location, and match) and other custom props you should use the render function prop.
Example:
<Route
path={`${SPA_PREFIX}/cases/:caseToken`}
render={routeProps => <CaseView {...routeProps} sarReport={sarReport} />}
/>
See Route render methods for further detail and explanation between the different methods for rendering routed components.
Going through resources on creating protected routes in React, I came across the following example
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
I can make use of the component by
<PrivateRoute exact path="/books" component={Book} />
So I've the following doubts on the above code segment
The protected route is passed as component but why's the Component tag used?
Also, if the rest of the properties are passed as ...rest to Route where does the render method gets its props from?
In Javascript, we can rename the key while destructuring using colon(:) as in component:Component The reason to do this is we cannot directly destructure Component since it is a reserved keyword in React.
2.
...rest is props for Route component. For example: In, <Route exact path="/books" component={Book} />, 'exact' and 'path' are props for Route component not for Book.
To pass props for Book component, react-router allows us to use render prop. render prop accepts a function and returns a component. react-router handles the passing of props from Route component to our Book component for us.
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!
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
This Route is using the render prop to render a child component with props:
<Route to='/path/:id' render={ () => (<ChildComponent myProp={myProp} match={this.props.match}/>)} />
But the version of match passed seems to be 'matching' against the parent component's route state and is thus not registering id underneath match.route.params.
I figured some solution like this might synchronize the route state:
<Route to='/path/:id' render={ () => (withRouter(<ChildComponent myProp={myProp} />))} />
But that just leads to an error saying that child components cannot be functions...
What's the correct way to deal with this?
Since you want to pass the match parameter, you shouldn't pass the one available to parent but the one relevant to the Route. render prop callback receives the Router Props which you can pass down
<Route to='/path/:id' render={(routerProps) => (<ChildComponent {...routerProps} />)} />
or you can simply write
<Route to='/path/:id' component={ChildComponent} />
in which case it will pass on the relevant router props to the component.
and in the child component you can access the match props like
this.props.match.params.id