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

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.

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

How can I create a subrouter with React Router v6?

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.

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

How to render a component that is passed as a prop in React?

I am tying to render my pages by using a loop instead of writing each of them separately.
I made this array to store the information for my pages.
const pages = [
{ title: "Translate", url: "/translate", component: TranslatePage },
{ title: "Study", url: "/study", component: StudyPage },
{ title: "About", url: "/about", component: AboutPage },
{ title: "Home", url: "/", component: HomePage },
{ title: "Terms", url: "/study/terms", component: TermsPage },
];
For each page, I need to render it in this format:
<Route
path="/study"
exact
element={
<StudyPage
getDocuments={getDocuments}
collectionRef={collectionRef}
docs={docs}
/>
}
/>
How can I render each page using currentPage.component to write the element part of the Route component? For each Component in my page array, I need to get
element={<ComponentReadFromPagesArray
getDocuments={getDocuments}
collectionRef={collectionRef}
docs={docs}
/>}
but I don't know how to pull the component from the array and then pass it in to element as its own component with props
If I understand your question correctly, it'd be like this:
const routes = pages.map(({component: Page, url: path}) => {
const element = (<Page {...{collectionRef, docs, getDocuments}} />);
return (<Route {...{element, exact: true, path}} />);
});

Cannot code nested routes with react-router-dom 5.2.0?

I'm trying to access to sub components with nested routing in React using react-router-dom 5.2.0.
Here you can find a CodeSandbox link of the project: https://codesandbox.io/s/nested-routes-8c7wq?file=/src/App.js
First, let me show you the tree structure of my pages.
Home
About
Organisations
MainContent
SecondContent
Routes for main pages (Home, About, Organizations) are in src/routes folder.
So their link look like:
https://localhost:3000/ (for Home)
https://localhost:3000/about (for About)
https://localhost:3000/organizations (for Organizations)
At this point, everything is okay for me. The problem is with the nesting part..
On Organizations page, I have to be able to switch between MainContent and SecondContent.
So links into that page must look like this:
https://8c7wq.csb.app/organizations/maincontent
https://8c7wq.csb.app/organizations/secondcontent
But I cannot print any of those contents...
If you want to build nested paths you should use the path and url from the current route match and build the nested routes and links from there.
src/pages/Orgainizations
import { Switch, Route, useRouteMatch } from "react-router-dom";
const Organizations = (props) => {
const { path, url } = useRouteMatch(); // <-- get current path and url
const [value, setValue] = useState("");
const menuLinks = {
data: [
{
id: 0, // <-- fix React key issue in NavBar
title: "StackOverflow",
to: `${url}/maincontent` // <-- build nested link
},
{
id: 1, // <-- fix React key issue in NavBar
title: "BitBucket",
to: `${url}/secondcontent` // <-- build nested link
}
]
};
return (
<div>
<NavBar changeValue={(value) => setValue(value)} menuLinks={menuLinks} />
<div>
<Switch>
<Route
render={() => <MainContent title={value} />} // *
exact
path={`${path}/maincontent`} // <-- build nested path
strict
/>
<Route
render={() => <SecondContent title={value} />} // *
exact
path={`${path}/secondcontent`} // <-- build nested path
strict
/>
</Switch>
</div>
</div>
);
};
* Notice I converted to rendering route components via the render prop as opposed to the component prop. When you use anonymous functions to render a component it is created each render cycle, i.e. it will remount the component each time. The render prop does not do this. See render methods for more information.
NavBar
Fix the to prop value to render the link correctly, remove the leading "/" from the path and render the direct link.to value.
Change
to={`/${props.link.to}`}
to
to={props.link.to}
Demo

Categories

Resources