Extend <Route> element of react-router-dom to run custom logic on route match - react-router

I'm using react-router v6 and I want to be able to have the following snippet in my code, and have document.title to be set to the value specified in title attribute of the Route element.
<Routes>
<Route path="/groups" element={<GroupsPage />} title='Groups - Student Portal`/>
<Route path="/classes" element={<ClassesPage />} title='Classes - Student Portal`/>
</Routes>
I can't seem to find a hook that would give me a currently matched route without parsing the routes myself or storing them as a javascript object.
Question #1: is there a way to get properties of a currently matched route?
Additionally, Routes implementation of createRoutesFromChildren link is not OCP-friendly. I can't think of a way to solve this without rewriting both Routes and Route with a slight modification just to allow extra properties on a route.
Question #2: how can I add custom properties to the Route element?

Related

React Router v6 Navigate to absolute path from subrouter

Suppose we have this situation:
<Routes>
<Route path="/" element={<Master/>}/>
<Route path="/login" element={<Auth/>}>
<Route path="" element={<AuthInitiate/>}/>
<Route path="callback" element={<AuthCallback/>}/>
</Route>
</Routes>
Suppose we currently are in /login/callback. Inside the AuthCallback component I use:
const navigate = useNavigate()
navigate("/")
The result is that i get redirected to /login, so I understand that the "/" refers to the relative subroute we are in (that is, /login). This is super useful in a lot of situations, but here I just to redirect with respect to the absolute path, so "/" should mean exactly / and not /login.
How should I do?
Be aware I'm referring to the new React Router v6.
EDIT: I had something else redirecting the route, the code instead works exactly as expected and navigate("/") from subroute indeed navigate to root level. There are no issues with navigation from subroutes, sorry.
Even though OP's problem is already resolved, I think it may be worth leaving this answer here for future travelers:
Use a leading slash to navigate to an absolute path:
const navigate = useNavigate();
// Absolute path:
navigate('/login/auth');
// Relative paths:
navigate('login');
navigate('login/auth');
navigate('../login');
Also noteworthy: In React Router v6 trailing slashes are ignored.

react-router-dom pass the Route props (match, location and history) to the child element when rendering using the child elements Not Working

The docs say:
Route render methods The recommended method of rendering something
with a is to use children elements, as shown above. There are,
however, a few other methods you can use to render something with a
. These are provided mostly for supporting apps that were built
with earlier versions of the router before hooks were introduced.
function You should
use only one of these props on a given . See their explanations
below to understand the differences between them. Route props All
three render methods will be passed the same three route props match
location history
But if I render with the recommended way as so:
<Router>
<Route exact path="/">
<Home />
</Route>
</Router>
Can actually the Component Home to access the Route props (location, match and history)?
If so, how can these props be passed or arrive to the Home component?
I just found out about the react-router hooks:
https://reacttraining.com/react-router/web/api/Hooks
So it is just as simple as importing the useLocation hook and use it:
let location = useLocation();
My apologies!

Why do <Link> and <Redirect> not trigger updates of state.router in connected-react-router?

After looking around at the various packages to combine redux with react-router, it looked as though connected-react-router was the most popular actively supported choice.
I configured it pretty much exactly as in the docs, with one exception described below.
The problem I am having is that a react-router <Redirect> does redirect, but it does not update state.router.location. Codesandbox MNWE is here. [Edit: I realized just after posting that <Link> is having exactly the same problem, and illustrated it in a slight modification of the original codesandbox. Sorry, would have been clearer if I'd just started there, but I need some sleep!]
In the following example, there are 3 routes. The first matches the root "/". The second matches simple URLs like "/anything" and the default will match things like "/anything/else". <Where> is a little component that shows its text prop and then shows state.router.location.pathname.
<Switch>
<Route exact path="/">
<Where text="Root" />
</Route>
<Route exact path="/:thing">
<Where text="Exact" />
</Route>
<Route>
<Redirect to="/" />
</Route>
</Switch>
When you arrive, you see "Root: /", as you should. If you add "/anything" in the address bar, you see "Exact: /anything". If you put "/anything/else" in the address bar, the address bar is redirected back to the root, and the <Switch> goes back to match the root, but instead of seeing "Root: /", you see "Root: /anything/else". The logger confirms that although the manually entered URLs trigger state updates, the <Redirect> does not.
Maybe I am wrong to think that it should, but I don't see it documented one way or the other. I imagine that I have made some configuration error?
[The configuration exception that I described is that I do not use combineReducers in my App, I have a single reducer which I call appRootReducer (which is empty in this example). So I wrote a little createRootReducer wrapper. It works well enough that the basic updates to state.router do go through.]
I hope that you can help!
Ack! Searching through closed issues in the Github repo, I see that I had left in my <BrowswerRouter> tags.
Wrap your react-router v4/v5 routing with ConnectedRouter and pass the history object as a prop. Remember to delete any usage of
BrowserRouter or NativeRouter as leaving this in will
cause
problems
synchronising the state.

React-Router v3 handling Child Routes data fetch

I am really struggling with ChildRoutes in react-router and any help direction would be much appreciated.
I want to define my routes something in this manner.
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
The main problem is with Users. So when user hits /users I load all the users from API.
And when he navigates to users/1 , I would read the data from Users state by getting the userId from routeParams and the fetch details of user 1.
I want the Child Routes to work as say if someone pastes the URL /users/1 directly in the browser then the route would still load my Users component which will load all the users and the waterfall flow will continue.
Now here is my issue , the user detail is completely a new page in my app, so even though the router loads Users compoennt I want to navigate and render User component if my URL exactly matches the pattern /users/:userId. I am not sure how I can achieve this with react-router v3.0.4 . So happy path is first component Users get loaded and then when user is clicked the path changes to users/:userId and the User Component renders.
But the complicated path is when someone loads users/:userId directly in the browser the Users component should get mounted and then based on the location.path the User component should get rendered.
Again Any help with this would be much appreciated.
Thanks
Why don't you create a parent component User that loads the data on entry, then put UserProfile (or whatever this is loading) as a child component... it would look something like this:
import React, { Component } from 'react';
const mapDispatchToProps = {
getDate: someAction.getData
}
#connect(state => state, mapDispatchToProps);
export default class User extends Component {
componentWillMount() { ...loadData }
render() { return {...this.props.children} }
}
Then in your routes
<Route path="users" component={Users}>
<Route path="user" component={User}>
<Route path="/:id" component={UserProfile} />
</Route>
</Route>
I could have gone in more detail here, but I think I explain the general concept. Let me know how it goes!
Using this method you can guarantee data is loaded every time.

Router.push() changes url but is caught in 404. Refresh same url works

I have a router something like this:
<Router history={browserHistory}>
<Route path="items">
<IndexRoute component={Items} />
<Route path=":id" component={ItemDetail} />
</Route>
<Route path="*" component={NotFound} />
</Router>
My Items component lists a bunch of items, each with a unique id.
The ItemDetail component... lets say just displays the props.params.id on screen.
When I click on an item, I call router.push('items/'+id) with the item's id.
The URL is updated correctly (e.g. */items/1234), yet it displays the NotFound component.
If I then press F5 (i.e. reload the same URL), it correctly displays the ItemDetail component.
Any idea why this is the case? The URL is the same for both.
(Note: If I change path=':id' to path='*' it goes to the correct page on router.push(), but I lose access to props.params.id, of course.)
(Note: If I change all the paths to use a / at the start, the navigation is very broken)
It appears that router.push() uses an absolute path (with a leading /) only.
Using the relative path as above caused the URL to update, but the router did not update so the page was not displayed. Pressing F5 caused the full route to be loaded, and therefore displayed the page.
Therefore I needed:
router.push('/items/'+id)