React nested route page not rendering properly as expected - javascript

I created this react component which has some nested routes. The problem is that the nested page component is either not rendering at all despite the URL changes or just returns a blank page.
I tried the suggestions from other posts like adding/removing exact in the parent route, but it's still not working.
Below are my codes:
// the parent component
<div className="App">
<Router>
<Navbar />
<Switch>
<Route exact path="/" render={() => <MyPage accessToken={accessToken} />}/>
<Route path="/editing/:playlistId" render={(props) =>
<EditingPlaylist {...props} accessToken={accessToken} />} />
</Switch>
</Router>
</div>
//the child component EditingPlaylist
render() {
const { pathname } = this.props.location;
return (
<div className="editing">
<Switch>
<Route exact path={pathname} render={() =>
<Searchbar accessToken={this.props.accessToken} />} />
<Route path={`${pathname}/test`} component={<p>Test</p>} />
<Route path={`${pathname}/album/:albumId`} render={
(props) => <AlbumPage {...props} accessToken={this.state.accessToken} />} />
<Route path={`${pathname}/artist/:artistId`} render={
(props) => <ArtistProfile {...props} accessToken={this.state.accessToken} />} />
</Switch>
</div>)
}
export default withRouter(EditingPlaylist);

Use url and path:
const { url, path } = this.props.match
to defined nested routes.
So, in nested routes:
<Switch>
<Route
exact
path={path} // HERE
render={() => <Searchbar accessToken={this.props.accessToken} />}
/>
...
</Switch>
There is a difference between url and path:
url: It is what is visible in the browser. e.g. /editing/123. This should be used in when redirecting via Link or history.push
path: It is what matched by the Route. e.g. /editing/:playlistId. This should be used when defining (nested) paths using Route.
From docs:
path - (string) The path pattern used to match. Useful for building nested <Route>s
url - (string) The matched portion of the URL. Useful for building nested <Link>s

Related

How to avoid re-initializing component when multiple linked routes use same component

I have a react-router-dom component that was all working fine in v5, and I have since upgraded to v6. In the below router structure, I have a single component that is accessed via different routes. This view has a playback feature that updates the URL when playback is stopped resulting in a foo/<fooId>/<time> url structure, and a means to link to something in the list with bar/<barId>. Both of these render the same component, that uses the params as need be and can handle both entry points.
The issue I'm seeing since moving to v6 is it will refresh if switching from foo/<fooId>/<time> to bar/<barId> or viceversa. Same happens if I click a link to bar/<barId> while on foo/<foodId>/time. It's the same component, and simply updated the browser URL in the past (used to use history.pushState and now use navigate (from useNavigate), I tried adding a replace param, etc., but it still re-mounts the entire component.
How can I avoid this? I can only think to re-structure the pages, but there's a bunch of bookmarks out there that will be very unhappy, haha.
<Route
path="/foo"
element={<AuthedRoute authState={authState} element={<Bar />} />}
>
<Route
path=":fooId"
element={<AuthedRoute authState={authState} element={<Bar />} />}
/>
<Route
path=":fooId/:timestamp"
element={<AuthedRoute authState={authState} element={<Bar />} />}
/>
</Route>
<Route path="/bar">
<Route
path=":barId"
element={<AuthedRoute authState={authState} element={<Bar />} />}
/>
</Route>
AuthRoute:
function AuthedRoute({ authState, element }) {
const location = useLocation()
const { isLoggedIn } = authState
return isLoggedIn ? (
element
) : (
<Navigate
to="/login"
state={{
from: location,
}}
/>
)
}
Similar structure to the example here
Shoot, ok, I hadn't noticed that you had nested "/foo" and "/bar" routes. That changes things a bit. I was able to reproduce the issue of switching between "/foo" and "/bar" routes. I still suggest converting the AuthedRoute into a layout component and rendering an Outlet for nested routes you want to protect.
function AuthedRoute({ authState }) {
const location = useLocation();
const { isLoggedIn } = authState;
return isLoggedIn ? (
<Outlet />
) : (
<Navigate
to="/login"
state={{
from: location
}}
/>
);
}
Next is to configure/structure your routes so the are correctly nested. The following structure seems to work as you are expecting.
<Routes>
<Route element={<AuthedRoute authState={authState} />}>
<Route path="/foo/*">
<Route index element={<Bar />} />
<Route path=":fooId" element={<Bar />} />
<Route path=":fooId/:timestamp" element={<Bar />} />
</Route>
<Route path="/bar/*">
<Route path=":barId" element={<Bar />} />
</Route>
</Route>
<Route path="/login" element={<Login /* auth props? */ />} />
</Routes>
For testing when Bar component was mounted vs rendered (an id prop was passed to each to identify them):
const Bar = ({ id }) => {
useEffect(() => {
console.log("Bar MOUNTED!!", { id });
}, []);
useEffect(() => {
console.log("Bar Rendered!!", { id });
}, [id]);
return <h1>Bar</h1>;
};
After authenticating, use the links to navigate between all the different routes and note when "Mounted" vs "Rendered" logs.
Here's my running codesandbox:

React Router v6 not working with URL Slugs

I'm trying to make react-router-dom work with a simple url: /user/{name} but for some reason cannot get it to load the page with the url slug for the name.
This is the return of my App function component:
<>
<MainNavBar navigation={navigation} />
<Routes>
<Route index={true} element={<Home />} exact />
<Route path="user" element={<User />} exact>
<Route
path=":name"
render={
({ match: { params: { name } } }) => {
console.log(name);
console.log("test2");
return (<UserPage
userName={name}
/>);
}}
/>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</>
This is the User component; a placeholder for my debugging atm.
const User = () => (
<div>
<header className="App-header">
<Outlet />
</header>
</div>
);
When I go to http://localhost:3000/user/test it loads the User component but not the children (the Outlet/UserPage elements)
I've tried lots of combinations but seem to be doing something wrong, so any help would be very appreciated. Thanks!
In react-router-dom v6 the Route components no longer have render or component props, they render their components on the element prop. Use the useParams hook to access the route match params. If UserPage is a component that can't use React hooks, then use a wrapper function component to access the route match param and pass it as a prop.
const UserPageWrapper = () => {
const { name } = useParams();
useEffect(() => {
console.log({ name }); // <-- log param in effect
}, [name]);
return <UserPage userName={name} />;
};
...
<>
<MainNavBar navigation={navigation} />
<Routes>
<Route index element={<Home />} />
<Route path="user" element={<User />}>
<Route path=":name" element={<UserPageWrapper />} />
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</>

How to prioritize the route in React?

I'm making React app, and I have some Routers.
const App = () => {
return (
<div>
<Header/>
<div>
<Route path="/LogIn" render={() => <LogIn />} />
<Route path="/Shop" render={() => <GoodsContainer />} />
<Route path="/Delivery" render ={() => <Delivery />} />
</div>
<Footer />
</div>
)
}
When I open my page for the first time, there're only Headercomponent and Footer component, and It's logically because the url has not contain pathes which I have set to the Routes.
So, my question is how to show always , for example the Route <Route path="/Shop" render={() => <GoodsContainer />} /> when the user open the site.
Redirect isn't my solution.
path prop could either be a string or an array of strings. You can define multiple paths for GoodsContainer component using an array of strings as a value for path prop
<Route path={["/", "/Shop"]} render={() => <GoodsContainer />} />
and don't forget to either use exact prop on Route component or wrap all Route component with Switch component otherwise / path will match all other routes.
You can check #Yousef answer for this , Also if you arent passing any props, then do this instead. Don't forget to give the exact
<Route path={["/", "/Shop"]} exact render={GoodsContainer} />
But if you are passing then do this
<Route path={["/", "/Shop"]} exact render={(props)=> <GoodsContainer {...props} />} />

Render Same Component With Multiple Paths React Router Dom

I was looking for the simplest way to render the same component but from different paths.
I have the following so that both "/" and "/login" render the Login component.
import React from "react";
import { Route, Switch, Redirect } from 'react-router-dom';
import './App.scss';
import Login from "../../login/Login";
const App = () => {
return (
<div className="App">
<div className="App-container">
<Switch>
<Route exact path={["/", "/login"]} component={() =>
<Login login={true} />}/>
<Redirect to="/" />
</Switch>
</div>
</div>
);
}
export default App;
This does appear to work, however, it returns an error in the console.
Warning: Failed prop type: Invalid prop 'path' of type 'array' supplied to 'Route', expected 'string'.
I'm trying to do this...
<Route exact path={"/"} component={() => <Login login={true} />}/>
<Route exact path={"/login"} component={() => <Login login={true} />}/>
But with a shorter method, is this possible with react-router-dom? Any help would be greatly appreciated
You could create an array that contains the paths / and /login and use map on that array to render the same thing for both paths.
<Switch>
{["/", "/login"].map(path => (
<Route
key={path}
exact
path={path}
render={() => <Login login={true} />}
/>
))}
<Redirect to="/" />
</Switch>
If you wish to render the same component on the several routes, you can do this by specifying your path as a regular expression
lets say you want to display 'Home' component for 'Home', 'User' and 'Contact' components then here is code.
<Route path="/(home|users|contact)/" component={Home} />

React-router: testing inside the `render` prop with enzyme

I'd like to test a redirection from the / path to a locale path (e.g. /en). So here's what the component looks like:
// GuessLocale is imported from a helper
const App = () => (
<Router>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/${guessLocale()}`} />
)} />
<Route exact path='/:lang' component={Home} />
</Switch>
</Router>
)
And this is the current testing function:
it('redirects to a localed path', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
)
expect(wrapper.find('Redirect')).toHaveLength(1)
})
Obviously, the test fails as the Redirect component is inside a child as a function as the render prop to the Route
In the test, I wrap the App in a memory router but in the App component, a browser router is already present so I might need to refactor that.
But even with the routes splitted in a Routes component, I don't know how to test inside the render prop.
You can test this by checking the component that should be rendered after the redirection, in this case the Home component like this:
it('redirects to a localed path', () => {
let wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/en`} />
)} />
<Route path='/en' component={Home} />
<Route render={() => "not found"} />
</Switch>
</MemoryRouter>
)
expect(wrapper.find(Home)).toHaveLength(1)
})
I had to remove <Router> to get this working since we're not using it for the browser. Another way of doing this is to check the <Route> pathname property within the location prop. see here:
it('redirects to a localed path', () => {
let wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/en`} />
)} />
<Route path='/en' component={Home} />
<Route render={() => "not found"} />
</Switch>
</MemoryRouter>
)
expect(wrapper.find("Route").prop('location').pathname).to.equal("/en")
})

Categories

Resources