useParams() is empty in react-router - javascript

I'm trying to render the params in the url as an h2 in in the website. But even when I try console.log useParams is empty.
Here's my Router.js file
const Webpages = () => {
return (
<Router>
<Routes>
<Route exact path="/" element={Home()} />
<Route exact path="/comics" element={Comics()} />
<Route path='/comics/:comicId' element={Comic()} /> <--------------
<Route exact path="/portfolio" element={Portfolio()} />
<Route exact path="/blog" element={Blog()} />
<Route exact path="/contact" element={Contact()} />
<Route exact path="/store" element={Store()} />
<Route path="*" element={NotFound()} />
</Routes>
</Router>
);
};
export default Webpages;
Here's my comic component
import React from 'react';
import NavBar from '../../components/NavBar';
import { useParams } from 'react-router-dom';
function Comic() {
let { comicId } = useParams();
console.log(comicId);
return (
<div>
<NavBar />
<p>{comicId}</p>
</div>
)
}
export default Comic
The page element when I go to a random comic works fine, like localhost:3000/comics/465456 but the tag is empty and the console log is undefined, it's also undefined if I just try to console log useParams()

You are directly invoking the React function. Directly invoking React functions is not how React works.
The Route component's element prop expects a React.ReactNode, a.k.a. JSX. Pass the components as JSX. The JSX is transpiled down to vanilla Javascript and the React framework handles calling your function within the confines of the React component lifecycle.
Example:
const Webpages = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/comics" element={<Comics />} />
<Route path='/comics/:comicId' element={<Comic />} />
<Route path="/portfolio" element={<Portfolio />} />
<Route path="/blog" element={<Blog />} />
<Route path="/contact" element={<Contact />} />
<Route path="/store" element={<Store />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
};

Related

Changing from v5 to v6, how to make it work with relative paths?

My application has scenarios where we need several routes to "pass" through a component to only then render the specifics, not only that but also situations where something is shown for the "parent" route and then split for the children...
It is imperative to note that we don't have a single "route config" file, and instead our routes are where we need them.
This was possible with v5, but I am very confused about how to get this accomplished with the new version.
So, currently we have stuff such as:
App.js
function App = () => {
return (
<Switch>
<Route exact path={['/', '/2', '/more-info']} component={Login} />
<Route path="/(main|settings|notifications)" component={AuthenticatedUser} />
<Redirect from="*" to="/404" />
</Switch>
);
}
AuthenticatedUser.js
function AuthenticatedUser= () => {
{... lots of common code}
return (
<div>
{...common html}
<Switch>
<Route exact path="/main" component={Main} />
<Route path="/settings" component={Settings} />
<Route path="/notifications" component={Notifications} />
</Switch>
</div>
);
}
Settings.js
function Settings= () => {
{... lots of common code}
return (
<div>
{...common html}
<Switch>
<Route exact path="/settings/basic" component={Basic} />
<Route exact path="/settings/notifications" component={Notifications} />
</Switch>
</div>
);
}
Now, with the relative from the parent, I am not able to get the same structure, I am also confused about how to get the routes split into separate files not even talking about the regex situation that I am guessing the solution is to duplicate the lines as many times as I have items in that regex...
You have basically 2 options when it comes to declaring the routes and sharing common UI:
Use layout routes and nested Route components.
Render routed components that render descendent routes in another Routes component wrapping descendent Route components.
Using layout and nested routes
Convert AuthenticatedUser into a layout route. Layout routes render an Outlet for nested routes to render their matched element into.
import { Outlet } from 'react-router-dom';
function AuthenticatedUser = () => {
{... lots of common code}
return (
<div>
{...common html}
<Outlet />
</div>
);
};
Convert Settings also into a layout route component.
import { Outlet } from 'react-router-dom';
function Settings = () => {
{... lots of common code}
return (
<div>
{...common html}
<Outlet />
</div>
);
};
App
import { Routes, Route, Navigate } from 'react-router-dom';
function App = () => {
return (
<Routes>
<Route path="/" element={<Login />} />
<Route path="/2" element={<Login />} />
<Route path="/more-info" element={<Login />} />
<Route element={<AuthenticatedUser />}>
<Route path="/main" element={<Main />} />
<Route path="/settings" element={<Settings />}>
<Route
path="basic" // "/settings/basic"
element={<Basic />}
/>
<Route
path="notifications" // "/settings/notifications"
element={<Notifications />}
/>
</Route>
<Route path="/notifications" element={<Notifications />} />
</Route>
<Route path="*" element={<Navigate to="/404" replace />} />
</Routes>
);
};
Using descendent routes
Here the parent routes need to render their route path with a trailing "*" wildcard matcher so descendent routes can also be matched. Descendent Routes components build their route paths relative to their parent Route path. I'd still suggest using AuthenticatedUser as a layout route for ease, otherwise you'll have a lot of code duplication since you'd need to wrap each route individually.
App
import { Routes, Route, Navigate } from 'react-router-dom';
function App = () => {
return (
<Routes>
<Route path="/" element={<Login />} />
<Route path="/2" element={<Login />} />
<Route path="/more-info" element={<Login />} />
<Route element={<AuthenticatedUser />}>
<Route path="/main" element={<Main />} />
<Route path="/settings/*" element={<Settings />} />
<Route path="/notifications" element={<Notifications />} />
</Route>
<Route path="*" element={<Navigate to="/404" replace />} />
</Routes>
);
};
Settings
import { Routes } from 'react-router-dom';
function Settings = () => {
{... lots of common code}
return (
<div>
{...common html}
<Routes>
<Route
path="/basic" // "/settings/basic"
element={<Basic />}
/>
<Route
path="/notifications" // "/settings/notifications"
element={<Notifications />}
/>
</Routes>
</div>
);
};

Uncaught Error: [RouteWrapper] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I'm using React Router v6 and am creating private routes for my application.
In file Route.js, I've the code
export default function RouteWrapper({
element: Element,
isPrivate,
...rest
}) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Route {...rest} render={(props) => <Element {...props} />} />;
}
And in file index.js I've written as:
return (
<Routes>
<Route path="/" element={SignIn} />
<Route path="/register" element={SignUp} />
<Route path="/dashboard" element={Dashboard} isPrivate />
<Route path="/profile" element={Profile} isPrivate />
<Route path="/customers" element={Customers} isPrivate />
<Route path="/new" element={New} isPrivate />
<Route path="/new/:id" element={New} isPrivate />
</Routes>
);
}
Is there something I'm missing?
Issue
RouteWrapper isn't a Route component, and fails an invariant check by react-router-dom.
RouteWrapper is directly rendering a Route component, which if the first invariant wasn't failed would trigger another invariant violation. Route components can only be rendered directly by the Routes component or another Route component in the case of building nested routing.
In short, in react-router-dom#6 custom route components are no longer supported. You should instead use wrapper components/layout routes to handle this use case.
Solution
Convert RouteWrapper to a wrapper component that renders an Outlet component for nested routed components to be rendered into.
Example:
import { Navigate, Outlet } from 'react-router-dom';
export default function RouteWrapper({ isPrivate }) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Outlet />; // <-- nested routes render here
}
Wrap the routes you want to protect with the RouteWrapper.
return (
<Routes>
<Route element={<RouteWrapper />}>
<Route path="/" element={<SignIn />} />
<Route path="/register" element={<SignUp />} />
</Route>
<Route element={<RouteWrapper isPrivate />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/customers" element={<Customers />} />
<Route path="/new" element={<New />} />
<Route path="/new/:id" element={<New />} />
</Route>
</Routes>
);
See Layout Routes for further details.
You should convert
<Routes>
<Route exact path="/" element={Dashboard} />
</Routes>
to
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
Also if you want to keep your UI in sync with the URL use like this.
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
</BrowserRouter>
Best.

React Router v6 error: All component children of <Routes> must be a <Route> or <React.Fragment>

The following React routes code probably works in React Router v5, but gives the following error in React Router v6
Error: [Player] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
Is it possible to update the Routes/Route code so that it works in React Router v6?
function App() {
// Some stuff here...
const { players, offlinePlayers } = usePlayers();
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/players">
{players.map((player) => {
return (
<Route exact key={player.name} path={`/players/${player.name}`}>
<Player player={player} />
</Route>
);
})}
</Route>
</Routes>
</BrowserRouter>
</ThemeProvider>
)
}
The Player component should be rendered by a Route component on the element prop, not as a child of the Route.
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/players">
{players.map((player) => (
<Route
key={player.name}
path={`/players/${player.name}`}
element={<Player player={player} />}
/>
))}
</Route>
</Routes>
</BrowserRouter>
You should map Routes in their parent route.
Like:
<Route path="/players">
{players.map((player) => (
<Route exact key={player.name} path={`/players/${player.name}`}>
<Player player={player} />
</Route>
);
)}
</Route>
But if you want to render dynamic player then dont use the above code for that purpose because its not best approach if you are using dynamic player.name. In your code you are creating each route for every player.
So, use the following code:
<Route path="/players">
<Route exact path={":playerName"} element={<Player/>} />
</Route>
And in Player component, extract playerName from params like:
let { playerName } = useParams();

Nested route in React Router reloads the entire page

I am trying to setup nesting in the react router. I have the following code:
import React from 'react';
import DefaultSwitch from './components/DefaultSwitch/DefaultSwitch';
import './scss/App.scss';
const App = () => {
return (
<DefaultSwitch />
);
};
export default App;
With DefaultSwitch defined as:
const DefaultSwitch = () => {
return (
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
);
}
Inside the Dashboard I have the following:
const Dashboard = () => {
return (
<div>
<MyNavbar />
<DashboardSwitch />
</div>
);
};
And finally DashboardSwitch as:
const DashboardSwitch = () => {
return (
<Switch>
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
);
}
Routing appears to work and the correct components are loaded, however I have noticed that if for example I am at /dashboard and then navigate to /dashboard/bonuses the entire page is reloading including the MyNavbar component. I want the navbar to remain static and only the content below it to reload as I have defined in the Dashboard component.
What am I doing wrong here?
Consider using a layout common to all components or something like this to avoid lose MyNavbar, for example:
const App = () => (
<BrowserRouter>
<Layout>
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
</Layout>
</BrowserRouter>
)
const Layout = ({ children }) => (
<div>
{children}
<MyNavbar />
</div>
);

Using multiple layouts for react-router components

If I have the following:
<Route path="/" component={Containers.App}>
{ /* Routes that use layout 1 */ }
<IndexRoute component={Containers.Home}/>
<Route path="about" component={Containers.About}/>
<Route path="faq" component={Containers.Faq}/>
<Route path="etc" component={Containers.Etc}/>
{ /* Routes that use layout 2 */ }
<Route path="products" component={Containers.Products}/>
<Route path="gallery" component={Containers.Gallery}/>
</Route>
How can I make it so that the two sets of routes each use a different layout.
If I only had a single layout then I would put it in App, but in this case where do I define the layout?
To make it even more complicated some of the layout components (eg top nav) are shared between both layout types.
You can use routes without a path to define containers that are not defined by the url:
<Route path="/" component={Containers.App}>
{ /* Routes that use layout 1 */ }
<Route component={Containers.Layout1}>
<IndexRoute component={Containers.Home}/>
<Route path="about" component={Containers.About}/>
<Route path="faq" component={Containers.Faq}/>
<Route path="etc" component={Containers.Etc}/>
</Route>
<Route component={Containers.Layout2}>
{ /* Routes that use layout 2 */ }
<Route path="products" component={Containers.Products}/>
<Route path="gallery" component={Containers.Gallery}/>
</Route>
</Route>
The layout components can then import additional components such as the top nav
Route's path property has accepted an array of strings for a while now. See https://github.com/ReactTraining/react-router/pull/5889/commits/4b79b968389a5bda6141ac83c7118fba9c25ff05
Simplified to match the question routes, but I have working multiple layouts essentially like this (using react-router 5):
<App>
<Switch>
<Route path={["/products", "/gallery"]}>
<LayoutTwo>
<Switch>
<Route path="/products" component={Products} />
<Route path="/gallery" component={Gallery} />
</Switch>
</LayoutTwo>
</Route>
{/* Layout 1 is last because it is used for the root "/" and will be greedy */}
<Route path={["/about", "/faq", "/etc", "/"]}>
<LayoutOne>
<Switch>
<IndexRoute component={Home} />
<Route path="/about" component={About} />
<Route path="/faq" component={Faq} />
<Route path="/etc" component={Etc} />
</Switch>
</LayoutOne>
</Route>
</Switch>
</App>
This solution prevents re-mounting the layouts on route changes, which can break transitions, etc.
Here's a great way to use multiple layouts with different React components.
In your router you can use:
<Router history={browserHistory}>
<Route component={MainLayout}>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
</Route>
<Route component={EmptyLayout}>
<Route path="/sign-in" component={SignIn} />
</Route>
<Route path="*" component={NotFound}/>
</Router>
Source: https://sergiotapia.me/different-layouts-with-react-router-71c553dbe01d
Pintouch, I was able to get this working with the following example:
Layout1:
import React from 'react'
const Layout1 = (props) => (
<div>
<h1>Layout 1</h1>
{props.children}
</div>
)
export default Layout1
Layout2:
import React from 'react'
const Layout2 = (props) => (
<div>
<h1>Layout 2</h1>
{props.children}
</div>
)
export default Layout2
Layout Container:
import React from 'react'
const LayoutContainer = (props) => (
<div>
{props.children}
</div>
)
export default LayoutContainer
Routes:
import React from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import LayoutContainer from './LayoutContainer'
import Layout1 from './Layout1'
import Layout2 from './Layout2'
import ContactManagerView from './ContactManagerView'
import CallerIdView from './CallerIdView'
import NotFound from './NotFound'
<Router history={hashHistory}>
<Route path="/" component={LayoutContainer}>
<Route component={Layout1}>
<IndexRoute component={DashboardView}/>
<Route path='Contacts' component={ContactManagerView}/>
</Route>
<Route component={Layout2}>
<Route path='CallerId' component={CallerIdView}/>
</Route>
<Route component={Layout}>
<Route path='*' component={NotFound}/>
</Route>
</Route>
</Router>
This is how it's done in React Router v6:
<Route path="/" element={<App />}>
{/* Routes that use layout 1 */}
<Route element={<BlueLayout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="faq" element={<Faq />} />
<Route path="etc" element={<Etc />} />
</Route>
{/* Routes that use layout 2 */}
<Route element={<RedLayout />}>
<Route path="products" element={<Products />} />
<Route path="gallery" element={<Gallery />} />
</Route>
</Route>
I've created a complete example in StackBlitz here.
You could create a function RouteWithLayout that renders the <Route> wrapped within the layout:
const RouteWithLayout = ({ component: Component, layout: Layout, ...rest }) => (
<Route {...rest} render={props => (
<Layout>
<Component {...props} />
</Layout>
)} />
)
const MainLayout = props => (
<div>
<h1>Main</h1>
{props.children}
</div>
)
const AltLayout = props => (
<div>
<h1>Alt</h1>
{props.children}
</div>
)
const Foo = () => (
<p>Foo</p>
)
const Bar = () => (
<p>Bar</p>
)
const App = () => (
<div>
<Switch>
<RouteWithLayout exact path="/foo" layout={MainLayout} component={Foo} />
<RouteWithLayout exact path="/bar" layout={AltLayout} component={Bar} />
</Switch>
</div>
)
I came across this question and found a solution that I want to share.
With react router v4 we could render the routes directly in your layout. Which is more readable and easy to maintain.
Layout
export class MainLayout extends React.PureComponent {
render() {
return (
<div>
<Header />
{this.props.children}
<Footer />
</div>
);
}
}
Mainlayout.propTypes = {
children: PropTypes.node.isRequired,
}
Router
export default function App() {
return (
<Switch>
<MainLayout>
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</MainLayout>
<OtherLayout>
.... other paths
</OtherLayout>
</Switch>
);
}

Categories

Resources