Why is my custom hook not re-initialised on path change? - reactjs

I am using ReactRouter to route the application (BrowserRouter at the top level) with a Switch that includes all the routes.
In my use-case I want to be able to handle paths that include the path-parameters (bedId) and navigate between different sub-paths (i.e. /beds/:bedId/, /beds/:bedId/info/) as well asa case where the path is (/beds/-).
I also want to be able to direct user to a different "bed" while they are already on some bed, so /beds/bed1/info -> /beds/bed2, and so on...
My BedView component is responsible for routing within that /beds/:bedId path like so:
// App.jsx (fragment)
<Switch>
<Route
path="/"
exact
render={() => (<Redirect to="/beds/-"/>)}
/>
<Route
path="/beds/-"
exact
component={SomeOtherComponent}
/>
<Route
path="/beds/:bedId"
component={BedView}
/>
</Switch>
The problem occurs when I try to use a hook that relies on the current path-parameter to fetch the latest data (i.e. call to /beds/bed1 will result in a call to http://myapi.com/beds/bed1/timeseries). The useLatestData hook is called from the BedView component, which look like so:
// BedView.jsx (fragment)
export default function BedView(props) {
const {bedId} = props.match.params;
let {path, url} = useRouteMatch('/beds/:bedId');
const bedData = useLatestData({
path: `/beds/${bedId}/timeseries`,
checksumPath: `/checksum/timeseries`,
refresh: false
});```
if(!bedData){
return <Loading/>;
}
return (
<Switch>
<Route exact path={path}>
<Redirect to={`${url}/info`}/>
</Route>
<Route exact path={`${path}/info`} >
<SomeComponent info={bedData.info} />
</Route>
</Switch>
}
...and the useLatestData hook is available here.
The problem is the fact that upon redirecting from /beds/bed1/info to /beds/bed2/info, the hook does not update its props, even though the BedView component seems to be re-rendering. I have created a version of the hook that 'patches' the problem by adding an useEffect hook in my custom hook, to detect the change in path (as supplied in the function arguments) and set data to null, but then the behaviour changes on the BedView.jsx's end - making the following check fail:
if(!bedData){
return <Loading/>;
}
I'm not entirely sure which part is the culprit here and any guidance would be much appreciated! As far as I'm aware, the hook is no re-initialised because the path change still results in the same component. There is also one caveat, once I change the BrowserRouter to include the forceRefresh flag, everything works fine. Naturally, I don't want my page to refresh with every redirect though...

try this:
const {bedId} = props.match.params;
let {path, url} = props.match;

Related

Passing Route path to the children element

<Route path="/pointandclick">
<MyComponent />
</Route>
For this piece of code, is there any way for my component to get the path of the Route that has been hit?
edit: to be more precise, let's say I want to know the string that has been hit (in this case /pointandclick) because in MyComponent I want to route between other paths, so I have to know if which path do I come from.
E.g.
<Route path="/pointandclick">
<MyComponent />
</Route>
<Route path="/draggablegame">
<MyComponent />
</Route>
and in my component I want to route on other components depending on the path. (example: if the Route that has been hit is pointandclick I want to render between game1, game2, game3 and if the Route is draggablegame I want to render between drag1, drag2, drag3 - so using the location hook might not be the best thing I think.
You're looking for a simple javaƛcript's window.location.pathname.
The useLocation hook returns the location object that represents the current URL. You can think about it like a useState that returns a new location whenever the URL changes.
import { useLocation } from 'react-router-dom'
function MyComponent() {
let location = useLocation();
console.log(location);
}
With useLocation(), you can get the active route. It's a hook that returns the location object that contains information about the current URL. Whenever the URL changes, a new location object will be returned.
Demo: https://codesandbox.io/s/use-location-demo-7d3c3
Read on: https://www.kindacode.com/article/react-router-uselocation-hook-tutorial-and-examples/

Get url as object in react

How can we get url as object not string.
This is my sample code below.
App.js
return (
<Switch>
<Route path='/:company/:project/:todo' component={Project} />
<Route path='/:company/:project' component={Project} />
<Route path='/:company' component={Projects} />
</Switch>
)
For example when a url is like companyxyz/project0/todo0. Somewhere in any of the components can get
{
company: companyxyz,
project: project0,
todo: todo0
}
Another example is companyxyz/project0. Then it will create like this.
{
company: companyxyz,
project: project0
}
As the examples above match their corresponding Route and map into object as Route is keys and Url is values
I use useLocation() but it returns a pathname with a url in string. I also use useParams() but returns empty object.
It is useParams() that can give us the expected results. The problem was, I use useParams() inside App.js which on this thread Can useParams be able to use inside App.js? can answer it.
For summary, useParams can only give us the object when we use it except in App.js

React Router: Redirecting URLs with a specific param

I'm updating a data repository site where datasets are mapped to an id, which is the param used in our url paths. A few datasets got corrupted recently and part of the solution involved changing their ids. Problem is, a lot of users are linked to datasets on our site - some of which are dead now that those aforementioned ids have changed.
For now, I'm just doing a quick client-side redirect on the 5 or so ids that are dead. I just want to redirect the user from /datasets/oldID to /datasets/newID but I can't find anything in the docs about literally redirecting to a different url. Yep, hardcoding it.
If www.example.com/rootpath/dataset/001 is dead and is now www.example.com/rootpath/dataset/002, how can I redirect the user FROM www.example.com/rootpath/dataset/001 and TO www.example.com/rootpath/dataset/002?
Here's the dataset routes setup
const DatasetRoutes = ({ dataset }) => (
<Switch>
<Route
name="dataset"
exact
path="/datasets/:datasetId"
render={() => <DatasetContent dataset={dataset} />}
/>
<Route
name="download"
exact
path="/datasets/:datasetId/download"
component={DownloadDataset}
/>
<Route
name="publish"
exact
path="/datasets/:datasetId/publish"
component={() => (
<Publish datasetId={dataset.id} metadata={dataset.metadata} />
)}
/>
/* ...
more routes etc
*/ ...
</Switch>
)
I'm kind of baffled that I can't figure out how to do something so presumably simple with React Router v4. I've tried several things...is there a straightforward solution to this?
You can handle the redirect in DatasetContent component:
Set up a dictionary mapping old ids that require redirecting to new ids.
Use the useParams hook (or however you are accessing the params) and in the DatasetContent component:
const map = {
oldId: "newId"
};
let { datatsetId } = useParams();
if (map.hasOwnProperty(datasetId) {
return (<Redirect to={`/datasets/${map[datasetId]}`});
}
// the rest of your original DatasetContent rendering code.
...

React router is not reloading page when url matches the same route

I have following route. When I'm at /createtest page and doing history.push(/createtest/some-test-id) as it matches the same route component, it is not reloaded. Is there any smart solution? Or I need to check match params and implement logic to reload?
(react 16, router v4)
<Route path="/createtest/:testid?/:type?/:step?/:tab?" render={(props) => <CreateTest user={user} {...props}/>}/>
You could give the URL parameters as key to the component so that an entirely new component will be created when a URL parameter changes.
<Route
path="/createtest/:testid?/:type?/:step?/:tab?"
render={props => {
const {
match: {
params: { testid, type, step, tab }
}
} = props;
return (
<CreateTest
key={`testid=${testid}&type=${type}&step=${step}&tab=${tab}`}
user={user}
{...props}
/>
);
}}
/>;
You can try this
const Reloadable = (props) => {
const { location } = props
const [key, setKey] = useState(location.key)
if (location.key !== key) {
setTimeout(() => setKey(location.key), 0)
return null
}
return <>{props.children}</>
}
<Route path="/" exact render={({ history, location }) => (
<Reloadable location={location}>
<SomeComponent />
</Reloadable>)}
/>
I think something like this might help solve your problem
<Route key={'notReloadableRoute'} ... />
(static key, like a regular string)
See this doc.
https://reacttraining.com/react-router/web/api/Route/exact-bool
The exact param comes into play when
you have multiple paths that have similar names:
For example, imagine we had a Users component that displayed a list of
users. We also have a CreateUser component that is used to create
users. The url for CreateUsers should be nested under Users. So our
setup could look something like this:
Now the problem here, when we go to
http://app.com/users the router will go through all of our defined
routes and return the FIRST match it finds. So in this case, it would
find the Users route first and then return it. All good.
But, if we went to http://app.com/users/create, it would again go
through all of our defined routes and return the FIRST match it finds.
React router does partial matching, so /users partially matches
/users/create, so it would incorrectly return the Users route again!
The exact param disables the partial matching for a route and makes
sure that it only returns the route if the path is an EXACT match to
the current url.
So in this case, we should add exact to our Users route so that it
will only match on /users:

how to tell react-router to wait until something has happened?

I want to configure react-router to wait for Firebase to login (or logout) before rendering any route.
I've seen react-router has an onEnter() hook, but it seems this is an immediate callback, and I haven't quite figured out how I would make react-router wait until firebase triggers an firebase.auth().onAuthStateChanged(...) event.
Does react-router include some functionality to handle such cases?
The onEnter hook accepts a third parameter, callback?, which if specified will block loading of the route until it is called.
An example from the react-router repo:
function requireCredentials(nextState, replace, next) {
const query = nextState.location.query
if (query.qsparam) {
serverAuth(query.qsparam)
.then(
() => next(),
() => {
replace('/error')
next()
}
)
} else {
replace('/error')
next()
}
}
and the render method:
render((
<Router history={withExampleBasename(browserHistory, __dirname)}>
<Route path="/" component={App}>
<IndexRoute component={Form} />
<Route path="page" component={Page} onEnter={requireCredentials}/>
<Route path="error" component={ErrorPage}/>
</Route>
</Router>
), document.getElementById('example'))
However, there is a warning about using this for long-running operations in the documentation:
Caution: Using the callback in an enter hook causes the transition to wait until it is called. This can lead to a non-responsive UI if you don't call it very quickly.
I fixed it using a main component in my hierarchy that prevents children to be rendered until something has happened.
Here is the implementation. Not the most elegant code, but it works in case you are desperate as I was.

Resources