React Router Link to same component with different URL not re-rendering - javascript

I have a component called Posts that lists all posts in a blog. I have links around the post's usernames which link to the same (Posts) component only with a different URL containing a user id to query the posts with. The problem is that the Posts component will not re-render upon visiting this URL, although the URL in the browser changes accordingly.
Routes in App.js:
<Routes>
<Route path='/' element={<Posts/>}/>
<Route path='/user/:user_id/posts' element={<Posts/>}/>
</Routes>
Links in Posts.js
<Link to={`/user/${post.user.id}/posts`}>
{post.user.username}
</Link>
I have tried various things including trying to add a key to the component but it didn't work. I also saw a "force re-render" code snippet but it was for a previous version of React Router.
How can I force the rendering of the Posts component in React Router v6?

If Posts is rendered by a route that has changing route params
<Routes>
<Route path='/' element={<Posts/>}/>
<Route path='/user/:user_id/posts' element={<Posts/>}/>
</Routes>
then it should "listen" for changes to the route's match params.
In Posts use the useParams hook to access the user_id route match param, and use an useEffect hook with a dependency on user_id to run/rerun any logic.
import { useParams } from 'react-router-dom';
...
const { user_id } = useParams();
useEffect(() => {
if (user_id) {
// do something with user_id
}
}, [user_id]);
If Posts is a class component, then either convert it to a function component so it can use React hooks, or create a wrapper component or a new withRouter replacement to use the hook and inject params as a prop.

I'm not sure but maybe this kind of code would work I think...?
<Routes>
<Route path='/' element={props => <Posts {...props} />}/>
<Route path='/user/:user_id/posts' element={props => <Posts {...props} />}/>
</Routes>
And if it does not works,
maybe just using <a> instead of <Link> might be work.
<a href={`/user/${post.user.id}/posts`}>
{post.user.username}
</a>

Related

Avoid re-rendering of parent components with react-router-dom v6 nested routes

In the process of upgrading my react app to react-router-dom v6, I had to refactor how the nested routing is handled. In the previous versions of react-router-dom, I managed to declare nested routes in child components, and that avoided re-rendering the parent component every time the route changed in the app.
The configuration could be summarized as something like this:
const Component = React.lazy(() => import('./path/to/Component'));
<Switch>
<Route path="..." component={Component} />
</Switch>
Then, inside the Component:
const NestedComponent = React.lazy(() => import('./path/to/NestedComponent'));
const Component = ({match}) => {
...
<Route path={match.url} component={NestedComponent} />
...
}
Following the upgrade guide for react-router-dom v6, I refactored the components above into something as the following:
const Component = React.lazy(() => import('./path/to/Component'));
<Routes>
<Route path=".../*" element={
<React.Suspense fallback={"loading 1..."}>
<Component />
</React.Suspense>
} />
</Routes>
Then in the component:
const NestedComponent = React.lazy(() => import('./path/to/NestedComponent'));
const Component = ({match}) => {
...
<Routes>
<Route path={match.url} element={
<React.Suspense fallback={"loading 2..."}>
<NestedComponent />
</React.Suspense>
} />
</Routes>
...
}
What I expected would have been that the Component component would not re-render while changing the route, only the NestedComponent (hence, showing only loading 2... inside the Component). But instead, when I change the route, all the components re-render. Also, I'm not sure whether with the new syntax (using element instead of component in the Route component) makes sense combined with React.lazy.
Is there a way to avoid the re-rendering of the parent component?
With the new syntax, does it make sense to use React.lazy? Or is there another way to lazily load components?

React Router V6 - NotFound component not working with dynamic parameter/slug?

I have installed "react-router-dom": "^6.0.0-beta.0" and created some page routes. Facing problems where the Page404 / NotFound component is not working if a page doesn't exist. For some reason when I am using dynamic page/post slug the component NotFound will not work if there is no page/post with this ID.
Is there an option inside React Router which solves this issue?
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import AllPosts from "components/AllPosts";
import SinglePost from "components/SinglePost";
import NotFound from "components/Page404";
const App = () => (
<Router>
<Routes>
<Route element={<AllPosts />} path="/" exact />
<Route element={<SinglePost />} path="/:slug" />
<Route element={<NotFound />} path="*" />
</Routes>
</Router>
);
export default App;
The Routes and Route component can't possibly know ahead of time that some dynamic path, "/someUnsupportedIdValue" for example, isn't a valid path to be handled by SinglePost.
The options you have here are:
Conditionally render some alternative UI in SinglePost, something along the lines of "A page by this ID isn't found".
Check if a page with matching ID exists, conditionally render that page, or a redirect (Navigate component replaced Redirect in v6) to your more generic 404 page (actually, a redirect to any "throw away" path that isn't explicitly handled already will land you on your 404 page). Or you can imperatively redirect with a navigate(to, { replace: true }).
Try to remove exact because was removed from v6 and make sure Page404 is the correct component or create Notfound.jsx
Check if the post not exists then redirect to Notfound page.

reactjs props getting lost in Route creation

I'm attempting to pass a user's auth state down to components via a react-router-dom switch block (I believe I ought to look at implementing redux but that's a question for later).
There's a Home view that gets passed all the login information after the user authenticates, and I can see the authState object as a prop in the home component using react devtools:
import React from "react";
import Dashboard from "../Dashboard";
import {Switch, Route} from "react-router-dom";
import NoMatch from "../NoMatch";
function Home(props) {
// authState exists
return (
<Switch {...props}>
// authState exists
<Route exact path="/" render={(...props) => <Dashboard {...props} />} /> // authState gone
<Route component={NoMatch} />
</Switch>
);
}
export default Home;
after executing this it renders the Dashboard, and tracing down the component chain with react devtools I can see that the prop has been passed from the Home component to the Switch component successfully. However, once it gets to the Route the prop is gone, and the only props available in the Dashboard component are the history, location and match props.
Why are the props missing and what's the right way to pass them down?
Couple of improvements needed in your code:
Passing props to Switch component is unnecessary
No need to collect router props using the rest syntax only to spread them later
Main Problem:
props inside the function passed to render prop refers to the router props instead of the props passed to Home component. You are using the same identifier for two different things.
Solution
Use different names for router props and the props passed to Home component that you want to pass down to Dashboard component
function Home(props) {
return (
<Switch>
<Route
exact
path="/"
render={(routerProps) => <Dashboard {...routerProps} {...props} />}
/>
<Route component={NoMatch} />
</Switch>
);
}
Alternatively, if you don't need access to router props in the Dashboard component, remove the props parameter from the render prop function.
<Route
exact
path="/"
render={() => <Dashboard {...props} />}
/>
Now, you won't have access to router props inside the Dashboard component but the auth state of the user will be passed down to Dashboard component.
In the most recent versions of the react-router-dom you must replace render and component attributes with element. You cannot pass a callback function there in which there were specified the route props anymore.
Instead you can use a hook in your routed component:
const params = useParams();
to obtain the parameters.
See more in the official documentation.

react-router-redux redirect to absolute url

I'm migrating a react application and I'm trying to split it. Basically, I would like to redirect some client-side react routes to absolute urls (or relative, but at least go with a server roundtrip, where reverse proxying is done)
Note that
react-router 3.0.0
react-router-redux 4.0.7
The app have these urls
http://myhost/ => homepage
http://myhost/someroute1 => a first route
http://myhost/someroute2 => a second route
http://myhost/someroute3 => a third route
Everything is inside react right now.
Routing looks like this :
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Root}>
<IndexRoute component={Home} />
<Route path="/someroute1" component={Route1} />
<Route path="/someroute2" component={Route2} />
<Route path="/someroute3" component={Route3} />
</Route>
</Router>
</Provider>
The goal is to redirect, say, routes "/" and "/someroute2" to static urls (server urls). As so :
http://myhost/ => http://anotherhost/
http://myhost/someroute1 => keep react routing
http://myhost/someroute2 => http://anotherhost/anotherroute5
http://myhost/someroute3 => keep react routing
The question is simple : is is possible to replace, in a clean way, a react router route with an absolute url ?
I heard about Redirect and IndexRedirect components, but I can't figure how to use it properly, and, due to a lack of react / react-router, I can't figure if there would be any dangerous side-effects (in history for example).
Use Route's render prop instead of component. That way, you can specify a function to be called instead of a component to be instantiated. In the function, perform the navigation the old-fashioned way, using window.location.href:
<Route
path="/someroute2"
render={() => {
window.location.href = "http://anotherhost/anotherroute5";
return null;
}}
/>
Partially based on #brub answer, I've found a solution using a dumb component.
import React, { Component } from 'react'
export default class MyRedirectRoute extends Component {
componentDidMount() {
window.location.href = //my url here
}
render() {
return null
}
}
That I pass like this
<Route path="/someroute3" component={MyRedirectRoute} />
Though, I'm still not aware of a few things :
Is this a recommended solution ?
Are there any history side-effect ?
Is there any better (more react-router) solution than window.location.href ? I tried this.context.history without any success...
I'm waiting for feedback / better solution before accepting my own answer
You probably don't need React Router for this. The creator of React Router suggests using the <a> tag.
I haven't tried it but syntactically you could do it like this:
<Route
path="/someroute2"
render={() => <Redirect to="http://anotherhost/anotherroute5" />}
/>

React Router route configuration that supports displaying a modal over any route

Consider the following React Router setup:
<Router history={history}>
<Route path="/" component={Root}>
<Route path="/users" component={UserList}/>
<Route path="/users/new" component={UserCreate}/>
<Route path="/users/:id" component={User}/>
<Route path="/tags" component={TagList}/>
<Route path="/tags/new" component={TagCreate}/>
<Route path="/tags/:id" component={Tag}/>
</Route>
</Router>
At any point, I want to be able to display a specific modal. Right now, I am managing this with a modal property in my Redux store. The Root component (<Route path="/" component={Root}>) checks for this property and renders the necessary modal if it is set.
Can this be achieved via React Router? So rather than having to keep a modal property in my redux store, I can just navigate to something like /users#modal or /users/modal and get both the UserList component rendered (as defined in <Route path="/users" component={UserList}/>) and the modal component
One of the ways you can do this would be to check for your location hash in your componentDidMount of your individual components.
componentDidMount() {
if (window.location.hash) {
const hash = window.location.hash.substring(1);
if (hash === "modal") {
//show your modal here.
}
}
}
You could have all your components inherit from a common base class if they all need to behave this way.

Categories

Resources