React router v5: need a nested route in dynamic routes - javascript

Happening specifically on react-router-dom v5.2.0
I have a list of items, all of which need their own page, and within that dynamic route, I would like some nested routes.
Think of it like this:
myapp.com/:item (main item page)
myapp.com/:item/about (about item page)
myapp.com/:item/faq (faq item page)
The thing is, with the way I'm implementing it, every nested /:item/whatever page gets a 404. Here is what my routing currently looks like:
const App = ({}) => {
return (
<Router>
<Switch>
<Route exact path='/:id' component={Item}>
</Route>
</Switch>
</Router>
)
}
const Item = ({ match }) => {
return (
<div>
TODO: Item {match.url}
<Switch>
<Route path={`${match.path}/about`}>
<h1>TODO: About</h1>
</Route>
<Route path={`${match.path}/faq`}>
<h1>TODO: FAQ</h1>
</Route>
</Switch>
</div>
);
};
As it stands, I can get the pages for /:item, but if I try to go to their nested routes, I get 404s. Is something wrong in my setup? How can I get this to work?
Take note that I've tried a bunch of different variations of this:
Nested routes in the App component
With and without the Switch wrapper
Passing the component into the Route as a component prop for the nested ones
Edit: decided to include a version of my Item component where I have every variation of a Route that I could think of for the /about route. Absolutely nothing works, which it should according to the docs (see the recursive paths) and I am starting to question my sanity
const Item = ({ match }) => {
const { url } = useRouteMatch();
return (
<div>
TODO: Item {match.url}
<Route path={`${url}/about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`/about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`:about`} component={() => <h1>TODO: About</h1>}/>
<Switch>
<Route path={`${match.path}/about`}>
<h1>TODO: About</h1>
</Route>
<Route path={`${url}/about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`/about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`/:about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`about`} component={() => <h1>TODO: About</h1>}/>
<Route path={`:about`} component={() => <h1>TODO: About</h1>}/>
</Switch>
</div>
);
};

So it would seem I was going about this the completely wrong way.
For what I wanted to do, my nested routes needed to be on the App component, or at least on the root of the first Switch wrapper.
So basically this was the solution:
const App = ({}) => {
return (
<Router>
<Switch>
<Route exact path='/:id' component={Item}/>
<Route exact path='/:id/about' component={() => <div>TODO: About</div>}/>
<Route exact path='/:id/faq' component={() => <div>TODO: FAQ</div>}/>
</Switch>
</Router>
)
}
I still am confused because the docs showed it differently, but anyway, this is what solved the issue.

Related

React router v6, group routes by feature

I am using React router v6.
THE PROBLEM:
my App.tsx is very big as it includes routes for all of the application:
Ex.
...
<Route path="products/" element={<ProductsList />} />
<Route path="products/:slug" element={<ProductView />} />
... about 300 lines
I would like to group these routes by feature so that I end up having something like this:
...
<Route path="admin" element={<AdminRoutes />} />
<Route path="products" element={<ProductsRoute />} />
...
it would look cleaner and easier to read.
So far I have created something like this for the Admin section:
export const AdminRoutes = (): any => {
return (
<Routes>
<Route path="admin" element={<Admin />}>
</Routes>
)}
and I have imported it like this inside App.tsx:
...
<Route element={<AdminRoutes />} path="admin" />
...
I am expecting to see the <Admin /> component (defined in AdminRoutes), although I don't get any errors the screen is blank.
export const AdminRoutes = (): any => {
return (
<Routes>
<Route path="admin" element={<Admin />}>
</Routes>
)}
Since you're using relative paths, the actual url that this will match is /admin/admin, one comes from the top level route in App, and another from here. Assuming you wanted this to only match "/admin", you can instead do:
<Route path="*" element={<Admin />}/> // Matches /admin
<Route path="dashboard" element={<Dashboard/>}/> // Matches /admin/dashboard
Or you could use an absolute path:
<Route path="/admin" element={<Admin />}/>
<Route path="/admin/dashboard" element={<Dashboard/>}

React Router. How to check if URL: ID exists in functional component?

Component is loaded on the correct path like /characters/kabal -- (kabal it`s ID)
But its loaded if you just enter any text after /characters/ for example /characters/548fnufndf or /characters/548fnufndf/dnbsdnhdj/dfmd
How to check the correct path in a functional component before loading the component and, if the path is wrong, redirect to another page?
//App.js
<Switch>
<Route
path="/characters"
exact
component={Characters}/>
<Route
exect
path="/characters/:id"
render={(props) => <CharacterPage {...props}/>}
/>
<Route
exect
path="/settings"
component={Settings}/>}
/>
<Route exect insecure component={Error} />
</Switch>
//Link to component and array of IDs
const item = [
{charId:'fujin'},
{charId:'scorpion'},
{charId:'kabal'}
]
<Link
exact="true"
to={{
pathname:`/characters/${item.charId}`,
}}
</Link>
//A component that should be loaded only if a link with this id exists.
const Scrollable = ({match}) => {
useEffect(() => {
let id = data[match.params.id]
if(!id) {
return <Redirect to="/" />
}
}, [])
}
What version of React-router are you using?
Here's a similar question: React-Router: No Not Found Route?
In summary
If you're using v4 or v5 then:
Keep the path (url stays unchanged)
<Switch>
<Route exact path="/users" component={MyComponent} />
<Route component={GenericNotFound} />
</Switch>
Redirect to another route (change url)
<Switch>
<Route path="/users" component={MyComponent} />
<Route path="/404" component={GenericNotFound} />
<Redirect to="/404" />
</Switch>
Pay attention to the order as well, NotFoundComponent must be last for that path. Also like what #cbr said, exect should be exact.

What's the best way to handle multiple languages via React Router v5?

I'd like to have multiple languages on my page. I'd like to do it via React and React Router v5. What's the best way to handle this?
This is simple code of my current routing:
const Routing = () => {
return (
<BrowserRouter>
<Header/>
<Route exact path='/' component={Home}/>
<Route path='/about' component={About}/>
<Footer/>
</BrowserRouter>
)
};
I would approach like this: have the site copy in external files for each language, e.g. /lang/en.json, /lang/fr.json etc. Put the copy for each page in an object in that json file so you can pass just that section to the component, e.g:
{
"about": {
"header": "About"
"intro": "..."
}
}
Detect or set the language and load the correct file and put it into the routers state:
componentDidMount() {
fetch(`/lang/${this.state.lang}.json`).then(response => response.json()).then(data => {
this.setState({ translations: data });
});
}
Then you pass what you need to the component as a property:
const Routing = () => {
return (
<BrowserRouter>
<Header/>
<Route exact path='/' component={ () => <Home copy={ this.state.translations.home } /> }/>
<Route path='/about' component={ () => <About copy={ this.state.translations.about } /> }/>
<Footer/>
</BrowserRouter>
)
};
With this approach you don't recreate the same component for multiple languages.
UPDATE:
To have a lang prefix in the URL you could simplify the whole thing:
const Routing = () => {
return (
<BrowserRouter>
<Header/>
<Route exact path='/:lang' component={ Home }/>
<Route path='/:lang/about' component={ About } /> }/>
<Footer/>
</BrowserRouter>
)
};
That parameter them becomes available in the component as this.props.match.params.lang which you can use to load the language file for that component, e.g. /lang/s{this.props.match.params.lang}/about.json
You can use /:lng before route like -
<Route path="/:lng/" exact component={Home} />
You can read more about it - https://medium.com/#markuretsky/react-router-multilingual-362eaa33ae20

How to use React Hooks to set state only at any first page visited by user?

I'm learning React Hooks and I need to set a state the very first page a user visits, and it must persist.
Right now I'm doing like this:
const Routes = () => {
const [state, dispatch] = useContext(StoreContext);
useEffect(async () => {
const rates = await updateRates();
dispatch({ type: "UPDATE_RATE", payload: rates });
}, []);
return (
<BrowserRouter>
<Route path="/" component={favicon} />
<Route path="/" component={navbar} />
<Switch>
<Route exact path="/" component={Main} />
<Route path="/signin" component={SignIn} />
<Route path="/signup" component={SignUp} />
<PrivateRoute path="/app" component={() => <h1>App</h1>} />
<Route path="*" component={() => <h1>Page not found</h1>} />
</Switch>
</BrowserRouter>
);
};
This is in my routes.js file. However this makes the state updates every other page the user visits. E.g., if user visits Main, then SignIn, the state is updated upon rendering Main and then updated again while rendering SignIn.
I tried to put in my App.js file, however it didn't work.
Thanks in advance!

react-router v4 nesting routing issue

I have a following situation:
<Wrapper>
<Container>
<Route exact path="/" component={UserListing} />
<Route path="/user/:id" component={UserDetails} />
<Route exact path="(/|/user/\d+)" component={StaticComponent} />
</Container>
<Route path="/create-account" component={CreateAccount}/>
</Wrapper>
Okey, so my case is simple: I don't want the Container component to render if path does not equal to "/" or "/user/:id".
So if path is "/create-account" only Wrapper with CreateAccount as child should appear, without Container.
Looking for some help. Thank you
You can write a custom component that decides whether or not to render Container like
const RouterRenderer = ({ location, children }) => {
if(location && location.state && location.state.noMatch) {
return null;
}
return children;
}
And a sub component which tells this container that nothing matched like
const NoMatch = () => {
return <Redirect to={{state: {noMatch: true}}} />
}
export default withRouter(NoMatch);
Now you can use them like
<Wrapper>
<RouterRenderer>
<Container>
<Switch>
<Route exact path="/" component={UserListing} />
<Route path="/user/:id" component={UserDetails} />
<Route exact path="(/|/user/\d+)" component={StaticComponent} />
<NoMatch />
</Switch>
</Container>
</RouterRenderer>
<Route path="/create-account" component={CreateAccount}/>
</Wrapper>
P.S. Note that its important to use Switch when you are using NoMatch component since you want to render it only when no other
route matched. Also you can write RouteRenderer function in the form
of an HOC instead of a functional component.

Categories