Can I avoid react-router forcing two renders on initial load? - javascript

I am using React Router where I need to send props down to the component I'm rendering. Due to this, I'm using this format:
<Route exact path={ROUTES.HOME} component={() => <HomeAuth ga={ga} />} />
This format for component= is causing componentDidMount to be run, and it then un-mounts before mounting again. This is a problem because I have a few promises that return after the un-mounting, meaning they're returning to a component that doesn't and will never exist anymore.
This implementation of component works for me, but I don't know if it's possible to pass props to it. Is there any way for me to either use the format above and avoid the second mounting, or pass props to this format?:
<Route exact path={ROUTES.HOME} component={HomeAuth} />

Found it! This is possible with render= instead of component= as written here: https://tylermcginnis.com/react-router-pass-props-to-components/
This doesn't create a new component on each render (because why should it!)
What a nightmare component= is!

Related

React router DOM : Switch not working if child element isnt a route

I'm facing an issue im not able to comprehend. I'm working on a React codebase and we started having too much Routes, so i decided to refactor it a little bit. There is some logic to determine if we should redirect to a route that will check if you're authenticated, and if not will redirect you to the sign in page. We also have basic routes without that mechanism that just display a component ( for every page that doesnt need authentication like sign in, forgot password etc. )
I've decided to abstract that logic in a different component, that will ultimatley render a Route. As a first step in that direction, i decided to wrap the rendering of every route in a single component, instead of having the whole logic just laying down there.
Here's the code for the routes:
<Switch>
{Object.values(userRoutes).map((route: SanitizedRoute) => (
<RouteController route={route} key={route.PATH} />
))}
</Switch>
RouterController.tsx
return (
<Route
path={props.route.PATH}
exact={props.route.EXACT}
render={() => {
return <Layout>{props.route.COMPONENT}</Layout>;
}}
/>
);
All the information for the Route component is passed down as a prop. This is where i start getting problems. If i try to access /path_b what is rendered is the first element of my userRoute array, eventhough the Route doesn't match; /path_b or /path_c or /path_whatever will always render the compononent defined for /path_a.
Accessing any path actually returns the /path_a component as if it was the only one present in my Switch component.
If i were to replace the RouteController component by its content as such :
<Switch>
{Object.values(userRoutes).map((route: SanitizedRoute) => (
<Route
path={route.PATH}
exact={route.EXACT}
key={route.PATH}
render={() => {
return <Layout>{route.COMPONENT}</Layout>;
}}
/>
))}
</Switch>
Then everything would work fine as expected. Wrapping my RouteController with a Switch component is also working fine - although im not sure about the side effect of having a Switch per route ?
{Object.values(userRoutes).map((route: SanitizedRoute) => (
<Switch key={route.PATH}>
<RouteController route={route} />
</Switch>
))}
My questions are:
Why isn't it possible to wrap all of those routes in a single ?
Is there some props from the component i should manually pass down to the component via my ?
I know that the is used to render the first route that match the path. Does having multiple kind of defeat the purpose of using a in the first place ?
Here's a Sandbox to display this behavior : https://codesandbox.io/s/react-router-dom-switch-test-forked-sqc98
I wrapper 1 route in a component. When using the wrapper nothing work. If i copy paste the wrapper content inside the Switch, it does.
Why isn't it possible to wrap all of those routes in a single
<Switch>?
This is because of how the Switch component works, it renders the first child <Route> or <Redirect> that matches the location.
Switch
The RouteController component is neither, so it's not involved in matching, and in fact only first component will be rendered. This is why you see only the first route rendered regardless of path.
Is there some props from the <Switch> component I should manually
pass down to the <Route> component via my <RouterController>?
It's possible to move/define a path prop (and any other "route" prop) on the RouteController the Switch may use for path matching, but this is ill-advised and not a documented use case of the Switch and Route components.
I know that the <Switch> is used to render the first route that
match the path. Does having multiple <Switch> kind of defeat the
purpose of using a <Switch> in the first place?
Yes, if you are rendering multiple Switch components, each with only a single Route component then they aren't really switching routes at this point and you're relying on the inclusive route matching of any wrapping router, or exclusively matching in the case of any other wrapping switches.
Solution
Instead of trying to map routes into your RouterController component and dealing with the issues of what is directly composing what, have your RouterController consume all the routes as a prop, and render them into a single Switch component.
RouterController.tsx
const RouterController = ({ routes }) => {
// any component business logic
return (
<Switch>
{routes.map((route: SanitizedRoute) => (
<Route
path={route.PATH}
exact={route.EXACT}
key={route.PATH}
render={() => <Layout>{route.COMPONENT}</Layout>}
/>
))}
</Switch>
);
};
...
<RouterController routes={Object.values(userRoutes)} />

Why aren't {...props} necessary when passing them to a component in a router in React?

I've started learning React recently and when I was following a course I stumbled upon something that confused me a lot. I decided to try it out separately to understand it and I don't get it.
I've looked it up and when you use a Route Tag in React, you do it like this:
<Route path="/" component={Component} />
But when you want to pass props, the syntax is the following:
<Route path='/dashboard' render={(props) => (
<Dashboard {...props} isAuthed={true} />
)}
/>
Which at first confused me, but then I got it. When testing, I did the following:
<Route path="/" render={() => (
<PropDrilling user={"Someone"} />
)}>
When I test this in the component, console.logging this.props, it works and I don't know why.
Thanks in advance!
React router uses React's render props where react router uses match, location and history. So, when you don't provide props in the render function, it will still be able to show you react component props. But it would not include match, location and history props provided by react router. To use them, you must provide props parameter in the render function.
Using an analogy, consider
function test(obj) {
console.log(obj);
}
test({a : 1});
function test() {
console.log({a : 1});
}
test();
These two functions log the same result, but one has no information about the data passed to console.log, while the other does.
Similarily, {...props} means you are forwarding the props received as a function parameter to a component, irrespective of what the value of props is.
Taking the example of <Route path="/" component={Component} />, you could write it like that :
const myProps = {
path: "/",
component: <YourComponent />
}
<Route {...myProps} />
Consider every props passed to a component as a key in an object. Here is a small repro on Stackblitz to show you how it works.

Why pass {...props} in a React Route?

I'm using the route below just because it was the code I found on the web:
<Route exact path="/test" render={(props) => <Test {...props} msg={ "abc" } /> } />
I know the {...props} denotes multiple arguments but I can't understand why I need it at all because the code below also works just fine and props.msg is available in Test expected
<Route exact path="/test" render={() => <Test msg={ "abc" } /> } />
So what does {...props} actually do when passed in during render?
From the documentation:
The render prop function has access to all the same route props (match, location and history) as the component render prop.
If Test is not using any of these then you don't have to pass them.
For your use case, your second code snippet is sufficient and will work just fine.
Spreading the props into the child component like
<Route exact path="/test" render={(props) => <Test {...props} msg={ "abc" } /> } />
makes sense if you want to pass properties to the child component that you are not handling yourself but receiving from another source like the Route-Component itself (in particular the route props "match", "location" and "history").
Keep in mind you can have parameters in your route, ex:
/users/:username
Those props will allow you to access it. In your case, you probably don't need it but you're better to always include them so it's consistent.
https://reacttraining.com/react-router/web/api/Route/route-props
more doc about the 3 props that are provided :
match https://reacttraining.com/react-router/web/api/match
location https://reacttraining.com/react-router/web/api/location
history https://reacttraining.com/react-router/web/api/history

router changed but component does not rerender

I'm using react-router v4 and redux. I have a react-router event like so
//src/App/components/common/Notifications
onCommentClick = (id) => {
this.props.history.push(`/dashboard/${id}`)
}
It will change the browser url, my route is setup correctly I guess
//src/App/containers/dashboard/user/NotificationDetail
renderUserRoute() {
return (
<Switch>
<Route exact path="/dashboard" component={UserAreaGuarded} />
<Route exact path="/dashboard/:id" component={NotificationDetail} />
</Switch>
)
}
but the problem is redux doesn't seem to rerender my container component, I need to refresh to get the right content.
I created a repo just to demo this issue https://github.com/thian4/hoc-redux, been stuck for so long for this, couldn't find any clue why it doesn't rerender.
The problem was not with the router, the NotificationDetail component invokes fetchNotification only in componentDidMount. As you are rendering the route components with the Route component prop, the NotificationDetail route component is mounted only once and then merely updated on every re-render.
There are two ways to fix this...
In NotificationDetail, do the fetchNotification stuff in componentDidUpdate instead of componentdidMount, simply rename the method. This approach is better.
Use a stateless component as the value of component prop in dashboard\index.js:
renderUserRoute() {
return (
<Switch>
<Route exact path="/dashboard" component={UserAreaGuarded} />
<Route exact
path="/dashboard/:id"
component={props => <NotificationDetail {...props} />} />
</Switch>
)
}
This will make React-Router render a new route component every time the route changes. Obviously this has performance issues over #1.
Unrelated to this, please add a key={i} on this line in Notification.js to fix the dreaded Each child in an array or iterator should have a unique "key" prop. warning.
Hope this helps.

React-router v4 unmounting when using render

Potentially a bit confused with how the render method works in react-router and what 'mounting' really means in React. I read the docs and it said that:
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 attribute, 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).
render: func
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.
So I expected that when the url changes, the existing component will be not be unmounted but instead stay there and hence save its state. But this is not what I observe. Here is my code:
<div style={styles.content}>
<Route path={`${this.props.match.url}/summary`} render={() => <ETFViewer/>} />
<Route path={`${this.props.match.url}/proxy`} render={() => <Proxy/>} />
</div>
So the components ETFViewer and Proxy get rendered fine and there's nothing wrong with them, but I notice that as I move from /proxy to /summary and back that the state is lost! Moreover, I can see in React devtools in Chrome that the component is indeed gone...
So what is going on here? Is there any way to allow the component to save its state and just "hide"?
Thanks for your help!
This question is a bit old but came up in a recent search of mine. I believe the issue is that you are not passing down your props along with the component. Here is an example:
<div style={styles.content}>
<Route path={`${this.props.match.url}/summary`} render={props => <ETFViewer {...props} />} />
<Route path={`${this.props.match.url}/proxy`} render={props => <Proxy {...props} />} />
</div>

Categories

Resources