React router v6 testing library navigate relative fails - reactjs

In my application i use relative navigation with react router v6 (we are currently migrating) and it works fine.
When i try to do some tests using Jest and testing-library the relative navigation fails and always resolve with the pathname '/'.
I use this wrapper to render my component under test to add it a Router.
const WrapTestComponentInRouter = (TestComponent, initialRoute = '/', history =['/']) => {
function WrappedComponent(props) {
return (
<MemoryRouter initialEntries={history}>
<Routes>
<Route path={initialRoute} element={<TestComponent {...props} />} />
<Route path="*" element={<Location />} />
</Routes>
</MemoryRouter>
);
}
return WrappedComponent;
};
function ComponentToTest() {
const location = useLocation();
console.log(location);
const path = useResolvedPath('..');
console.log(path);
return <button onClick={() => navigate('..')}>navigate</div>;
}
I use the hook useResolvedPath to compute the navigation result and log it. In my application the relative path computed is correct but in my test it is always equals to '/' whatever the current location is.
Does someone has encounter and solved this problem?
EDIT
Ok, i understand what is the problem. The useResolvedPath is using the routingContext to compute the relative route so if we use '..' it pop to the previous route in the route tree.
With that, if i want to test my navigation i need to reproduce my route tree. Another solution is to use the real relative path by using the method resolvePath('..', location)
The resolvePath compute relative path from a path
resolvePath('..', '/user/project') ---> '/user'
the navigate function also use the RoutingContext so if i want to do a relative from the current path i have to do
const location = useLocation();
navigate(resolvePath('..', location.pathname));

Related

react-router-dom v6: Route's render prop equivalent in v6

in my routes.js with react router v6 this block of code would work but now it's unable to render the component. Instead i get this error
Matched leaf route at location does not have an element. This means it will render an with a null value by default resulting in an "empty" page.
<Route
path="/callback"
render={(props) => {
context.handleAuth(props);
return <Callback />;
}}
/>
I'm trying to figure out how to pass the props into my callback component in v6
Version 6 favours element over components to. In version 6, you may do it this way:
Use the follwing in <Callback />
const params = useParams();
useEffect(() => {
/* If context is avialable here. */
context.handleAuth(params);
}, [])
For more info, read the doc here:
https://reactrouter.com/docs/en/v6/upgrading/v5#advantages-of-route-element
See more:
https://medium.com/frontendweb/how-to-pass-state-or-data-in-react-router-v6-c366db9ee2f4

React Router - Placing components by iteration not working

Given the following code:
const AdmNews = React.lazy(() => import('./components/adm/AdmNews'));
const AdmPeople = React.lazy(() => import('./components/adm/AdmPeople'));
const AdminRoutes = [
{path:"/route/admin/news",component:<AdmNews/>},
{path:"/route/admin/people",component:<AdmPeople/>},
]
{AdminRoutes.map((el)=>{
return <Route path={el.path}
element={
<React.Suspense fallback={<>...</>}>
{el.component}
</React.Suspense>
}/>
})}
I'm trying to iterate over objects in order to create the routes in react, and when I click on the matching buttons, they understand the path property, and it reacts accordingly, however, the component is not loading. Is is right to have the component inside the object as show above: component:<AdmNews/>? (No errors are shown.)
PD. If I place the code manually without iteration, it works, so I suspect of component:<AdmNews/> not being the right approach.

#cypress/react : How to launch a component under a given path / URL?

I have a React component 'Foo' that uses react-router to decide which child to mount.
Let's say this component has 3 submodules: A, B, C.
In a 'normal' cypress test I would use cy.visit('www.test.com/foo/subModuleA') and I would get my site and it would display my Foo-component, which would render the module 'A'.
My component test however mounts this component directly, and cy.visit(...) doesn't work.
cy.visit from a component spec is not allowed
see https://github.com/bahmutov/#cypress/react/issues/286
(Sadly this link leads to nowhere)
For the time being I wrote myself a helper-component:
function PathEnforcer (props: React.PropsWithChildren<{ path: string }>) {
const history = useHistory()
useEffect(() => {
history.push(props.path)
}, [])
return <>{props.children}</>
}
This simply switches the path as soon as it mounts.
Now I can do something like this in my test:
mount(
<HashRouter>
<PathEnforcer path='subModuleA'>
<Foo />
</PathEnforcer>
<HashRouter>
)
And my component is displaying the right module.
This is a workaround that I can work with, but it can hardly be a long term best practice.
How can I configure the tests / execute a command that directly mounts this component given the right path?
I was running into the same issue and this question came up. With react-router v6 this works:
mount (
<MemoryRouter initialEntries={['/foo']}>
<Routes>
<Route path={'/:segment'} element={<YourComponent />}>
</Routes>
</MemoryRouter>
)

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

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;

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:

Resources