How can I create a subrouter with React Router v6? - javascript

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.

Related

How to use React router v6.4 with typescript properly?

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 />,
},
]

React Router v6 routing with paths like https://adomain.com/blogs/sampleblog [duplicate]

This question already has an answer here:
Page layout broken in React Router v6
(1 answer)
Closed 4 months ago.
I've defined nested routes using createBrowserRouter and I expected that the router would route to my child page. However, it seems to stop at the top-level part of the url. BlogsPage and SampleBlog are both React components.
I expected "https://adomain/blogs/sampleblog" to get routed to SampleBlog not to BlogsPage. I thought I could nest as deeply as I liked and that the router would match the longest matching path and invoke that component.
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{ path: "", element: <HomePage /> },
{ path: "home", element: <HomePage /> },
{
path: "blogs",
element: <BlogsPage /> },
children: [
{ path: "sampleblog", element: <SampleBlog /> },
]
},
]
},
]);
children are used to render child-routes. You would need to use an <Outlet /> inside BlogsPage
What you wanted to do is creating two routes at the same level
[
{ path: "blogs", element: <BlogsPage /> },
{ path: "blogs/sampleblog", element: <SampleBlog /> }
]

Prevent falsy request from React Router loader function

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

cant access react-router params from parent route

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

How to pass string into react-router-dom Route as function?

I'm using React 16.11.0 and react-router-dom 5.1.2.
I have an array of objects representing pages like this:
const pages = [{ url: "/", component: "Home" }, {...}, ... ]
Update: Maybe I should mention this is from a JSON string so I don't get to determine the type of the values.
I want to dynamically generate Routes in React by mapping over it:
import Home from './components/Home';
...
<BrowserRouter>
<div className="App">
<Switch>
{pages.map(page => (
<Route path={page.url} component={page.component} />
))}
</Switch>
</div>
</BrowserRouter>
page.url works but URLs can be strings anyway. page.component is not loading properly, I think because it is a string whereas the imported Home is a React function component.
I've tried to use eval() and Function() to convert the page.component string into objects but they don't work.
I've tried the docs and search engines but haven't been able to figure out a solution. Maybe I'm searching in the wrong places.
Is this possible? How should I go about doing it?
Actually you can even avoid importing modules - require comes handy:
const pages = [{ url: "/", component: "Home" }, {...}, ...]
.map((obj) => (
{ url: obj.url, component: require(`./components/${obj.component}`).default }
);
And then following your logic:
<Switch>
{pages.map(page => (
<Route path={page.url} component={page.component} />
))}
</Switch>
Try setting page.component to Home.
Instead of this:
const pages = [{ url: "/", component: "Home" }, {...}, ... ]
Try this:
const pages = [{ url: "/", component: Home }, {...}, ... ]
For the solution the only part you need is to create components dynamically, so
const pages = [
{ url: "/", component: Home },
{ url: "/about", component: About }
];
Keep in mind components should be defined before such as Home, About etc.

Categories

Resources