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.
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.
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'm having an issue passing props through React elements (like Switch and Route). In the example below, I would like to pass all props of Dashboard component down to the Account component. Is there a way to achieve this?
App.js
<Dashboard>
<Switch>
// Dashboard props to Account component
<Route path="/account" render={props => <Account {...props} /> } exact />
<Route path="/someothercomponent" component={Someothercomponent} />
</Switch>
</Dashboard>
Dashboard.js
render() {
const children = React.Children.map(this.props.children, child => {
var router = React.cloneElement(child, { image: this.state.image });
return router;
// Like this the router Element does receive the image prop from
// the Dashboard component. Now this image prop needs to be
// passed on to the Account component.
}
I like some of the answers already present. To give you a sense of solving this problem differently and also something to learn and add to your toolbox. I would say use Context. Context provides a way to pass data through the component tree without having to pass props down manually at every level. https://reactjs.org/docs/context.html
So if you get to your Account and have to yet again pass props down this might be a good place to implement this.
When setting up correctly you could do something like this on your page. But again you aren't just passing down one you are passing down all props. And then what if you need to also pass them down on the next component <<< this is the point of Context. I would think using context is better than using a component as your state considering a stateful component is usually limited. With context, your Account component could have several children and you wouldn't have to pass props all the way down to get done what you wish to achieve.
<AppContext.Consumer>
{({prop1, prop2, prop3}) => {
}}
</AppContext.Consumer>
That's assuming you name your variable AppContext when you use React.createContext();
The idea is that passing down props at many levels can be annoying for some but using context you can bring a property in at any time without having to worry about if you passed them down correctly. Be sure to read the article in full there are times where you want to use context and times where you do not.
Yes, use render property instead.
<Route path="path" render={() => <MyComponent {...this.props} />} />
The problem is component is overriding the render props.
Remove component={Account}
I've also added brackets around (props) to improve readability
<Dashboard>
<Switch>
<Route
path="/account"
render={(props) => <Account {...props} /> }
exact
/>
<Route
path="/someothercomponent"
component={SomeOtherComponent}
/>
</Switch>
</Dashboard>
Alternatively:
const renderMergedProps = (component, ...rest) => {
const finalProps = Object.assign({}, ...rest);
return( React.createElement(component, finalProps)
);
}
const PropsRoute = ({ component, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return renderMergedProps(component, routeProps, rest);
}}/>
);
}
<Router>
<Switch>
<PropsRoute path='/login' component={Login} auth={auth} authenticatedRedirect="/" />
<PropsRoute path='/trades' component={Trades} user={user} />
</Switch>
</Router>
source
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