I'm using react-router version 6.2.1. My router is setup like so in a Routes component:
const routes = isLoggedIn => [
{
path: '/',
element: isLoggedIn ? <DashboardLayout /> : <Navigate to={LOGIN} /> ,
children: [
// redirect to homepage on null route (e.g. localhost:3000 with no trailing '/')
{ path: '', element: <Navigate to = {HOME} /> },
{ path: HOME, element: <Home /> },
{ path: LIBRARY,
element: <Library/>,
children: [
{
path: ':itemId',
element: <ItemView/>
}
]
}
}
]
export default function Routes(props) {
const loginStatus = props.loginStatus === LOGGED_IN;
const routing = useRoutes(routes(loginStatus));
return (
<>
{routing}
</>
);
}
I would like to access the itemId param in the url from a component that is in the Library component which is a parent route to the itemId. If i call the useParams hook from that parent route I do not see the itemId param. Why is this the case? Am i using the wrong version of react-router? According to this github pr I believe parent routes should have access to all params:
https://github.com/remix-run/react-router/pull/7963
Related
I'm trying to use react-router-dom v6.4+ in my project. I implemented it as a route array of objects. Its worked as routing but suddenly I got another issue realated this. I can't call any hook inside the Component which located on element property in route array.
In the route.ts file:
import MainLayout from './container/layouts/mainLayout/MainLayout'
import ErrorPage from './view/Error'
import Home from './view/Home'
const routes: RouteObject[] = [
{
path: '/',
element: MainLayout(),
children: [
{
index: true,
element: Home(),
},
],
},
{
path: '*',
element: ChangeRoute('/404'),
},
{
path: '/404',
element: ErrorPage(),
},
]
const router = createBrowserRouter(routes)
export default router
and in the app.ts file:
<RouterProvider router={router} fallbackElement={<React.Fragment>Loading ...</React.Fragment>} />
But If I try to use any hook , inside MainLayout component , its saying
code in MainLayout component :
const MainLayout = () => {
const [collapsed, setCollapsed] = useState(false)
return (
<Layout className='layout'>
<SideBar collapsed={collapsed} />
<Layout>
<Topbar collapsed={collapsed} setCollapsed={setCollapsed} />
<Outlet />
</Layout>
</Layout>
)
}
export default MainLayout
I think if I use element: <MainLayout/> instead of element: MainLayout(), then this issue will resolve. but typescript doesnt allow me to do this. and on the documentation every thing is on plain javascript. only one type defination there is this
How to solve this? Kindly guide me.
Edit
Here is the codesandbox demo : visit sandbox
Changes the name of the route.ts file to route.tsx, now you can will set components in the element object property, this works for me.
The element prop expects a React.ReactNode, you are directly calling a React function instead of passing it as JSX.
Example:
const routes: RouteObject[] = [
{
path: '/',
element: <MainLayout />,
children: [
{
index: true,
element: <Home />,
},
],
},
{
path: '*',
element: <ChangeRoute redirect='/404' />,
},
{
path: '/404',
element: <ErrorPage />,
},
]
Here is my current React Router implementation:
const router = createBrowserRouter([
{
path: "/",
element: (
<Page activeNav="home" >
<Home />
</Page>
)
},
{
path: "/about",
element: (
<Page activeNav="about" >
<About />
</Page>
)
},
{
path: "/blog",
element: (
<Page activeNav="blog">
<Blog />
</Page>
)
},
{
path: "/blog/:postName",
element: (
<Page activeNav="blog" >
<Post />
</Page>
),
loader: ({ params }) => params.postName
},
{
path: "/chess",
element: <ChessRouter />
}
])
The last route, /chess is of importance. I am looking to define routes such as /chess/play, /chess/login, /chess/register, etc. My initial idea was to just put another Router as the element for the /chess path and then all those paths would be routed from there. However, that throws an error saying:
You cannot render a <Router> inside another <Router>. You should never have more than one in your app.
I also tried using the children property on the /chess route but this does not render anything when I go to /chess/play etc.
What is the correct way of implementing subpaths (not sure of the correct word for it)?
It is correct that you cannot render a router component within another router component as this is an invariant violation. You need only one router and routing context per React app.
To render sub-routes on "/chess" then there are two options:
Render nested routes in the routes configuration declaration. This requires the ChessRouter component to render an Outlet component for nested routes to render their element content into. Nested routes will be able to use the new RRDv6.4 Data APIs.
const router = createBrowserRouter([
...,
{
path: "/chess",
element: <ChessRouter />,
children: [
...,
{
path: "play",
element: <ChessPlay />
},
... other chess sub-rotues
],
}
]);
const ChessRouter = () => {
...
return (
...
<Outlet />
...
);
};
Render a root route with trailing wildcard ("*") character that allows descendent routes to also be matched. This allows the ChessRouter component to render descendent routes, i.e. a Routes component with a set of Route components. Descendent routes will not be able to use the RRDv6.4 Data APIs.
const router = createBrowserRouter([
...,
{
path: "/chess/*",
element: <ChessRouter />,
}
]);
const ChessRouter = () => {
...
return (
...
<Routes>
...
<Route path="/play" element={<ChessPlay />} />
... other chess sub-rotues
</Routes>
...
);
};
If your ChessRouter does not contain any additional functionality besides route declarations, you could drop it altogether and use an index route.
Working off of what Drew had:
const router = createBrowserRouter([
...,
{
path: "/chess",
children: [{
index: true,
element: <ChessPlay /> // The default component to load at '/chess'
},
{
path: "play",
element: <ChessPlay />
},
... other chess sub-rotues
],
}
]);
This will let all of your child routes under /chess work as well as prevent an empty page from showing if a user hits the base /chess route.
I use React Router 6.4.2 and it's API - createBrowserRouter.
I have a default request that should be done only once when user reach main route ('/').
Request requires auth token (Amplify) so i use protected routes. No token - redirect to './auth/.
Router:
const router = createBrowserRouter([
{
element: (
<RequireAuth>
<AppLayout />
</RequireAuth>
),
children: [
{
path: '/',
loader: getDefaultData,
element: <MainPage />,
},
],
},
{ path: '/auth', element: <Authenticator /> },
{
path: '*',
element: <NotFoundPage />,
},
]);
export const AppRouter = () => {
return <RouterProvider router={router} fallbackElement={<AppLoader centered />} />;
};
RequireAuth:
export const RequireAuth = ({ children }: Props) => {
const location = useLocation();
const { route } = useAuthenticator((context) => [context.route]);
if (route !== 'authenticated') {
return <Navigate to="/auth" state={{ from: location }} replace />;
}
return <>{children}</>;
};
GetDefaultData:
export const getDefaultData = async () => {
store.dispatch(
getData({
someVariables,
})
);
};
What i faced: when not authenticated user try to reach main route ('/'), he reach it for a moment before he will be redirected to './auth/ and React Router run getDefaultData from loader that fails on getting-auth-toker step.
What i expect: React Router will skip getDefaultData for that case. Looking for a way how to tell React Router about that in a beautiful way.
P.S. I know that i can add auth check with return inside getDefaultData function (generally it happens but not from scratch but on getting-auth-token).
I know about shouldRevalidate but not sure that it can help me in that case.
UPD. provided a codesandbox for that
https://codesandbox.io/s/amazing-matsumoto-cdbtow?file=/src/index.tsx
Simply try remove '/auth' from url manually and check console.
UPD. created an issue about that https://github.com/remix-run/react-router/issues/9529
Got an answer from Matt Brophy in github:
Fetching is decoupled from rendering in 6.4, so loaders run before any rendering logic. You should lift your "requires authorization" logic out of the RequireAuth component and into your loaders and redirect from there. Redirecting during render is too late 😄
Also note that all loaders for a matched route route run in parallel, so you should check in each loader for now (just like in Remix). We plan to make this easier to do in one place in the future.
Original answer:
https://github.com/remix-run/react-router/issues/9529
Using useRoutes and Outlet. I have the following route example:
const routes = [
{
path: "/",
element: <Dashboard/>,
children: [
{ path: "/*", element: <Profile /> },
{ path: "/*/example", element: <Example/> },
],
}
]
const App= () => {
const route = useRoutes(routes)
return <div>{route}</div>
}
I want to push user_id to the url so for example /user1 this loads the component as expected however when I type something like /firstname.surname I get the follow error Cannot GET /firstname.surname however /firstname.surname/example does load the component
This is due to the full stop in the Id. How can I resolve this and accept it as a valid URL?
When I update path for dashboard to "" it seems to work however when I do something like /foo.bar/test it loads profile component how do I restrict this as this path does not exists?
I think the wildcard in { path: "/*/example", element: <Example/> } doesn't work well with other routes also specifying the wildcard. It's my understanding that the "/*" is valid only at the end of the path to indicate matching more deeply nested routes.
The following config seems to work though, using a named path param.
const routes = [
{
path: "/",
element: <Dashboard />,
children: [
{ path: "/*", element: <Profile /> },
{ path: "/:path/example", element: <Example /> }
]
}
];
const routes = [
{
path: "/",
element: <Dashboard/>,
children: [
{ path: "/:id", element: <Profile /> },
{ path: "/:id/example", element: <Example/> },
],
}
]
const App= () => {
const route = useRoutes(routes)
return <div>{route}</div>
}
"*" is a generic wildcard need to pass id like above
I currently have a Vue.js component called Summoner.vue which is rendered thanks to the router when a user visits the following URL - http://localhost:8080/summoner/username
In that component I have a <div> element which triggers a method on click, that sends the user to a new URL - http://localhost:8080/summoner/username/match/4132479262 which renders a different component Match.vue. Like this:
<div #click='specificMatch(match.gameId)'>
specificMatch(gameId){
router.push('/summoner/' + this.summoner + '/match/' + gameId)
}
Now all I want to do is pass an object as props from the first component to the second one, but I'm not sure how to do that because I'm using the router. Normally I'd pass props like this - <summoner v-bind:match="match.id"></summoner> but I guess that doesn't work in my case since I'm using router.
And these are my routes:
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/summoner/:summonerName',
name: 'summoner',
component: Summoner
},
{
path: '/summoner/:summonerName/match/:matchId',
name: 'match',
component: Match
}
]
})
Here is how you can do it with vue-router props :
{
path: '/summoner/:summonerName',
name: 'summoner',
component: Summoner,
props : true // now you can pass props to this route
}
then when you want to navigate to it :
this.$router.push({ name : summoner , params : { summonerName : this.summoner , somedata : 'hello' etc ... }})
now summoner component will have access to all these params on its props :
// summoner.vue
export default {
props : ['somedata',...]
...
}