how can i use array in routing with react-router - javascript

This is my routing with regex pattern by react-router.
ItemList component appears when the URL is /categories/cat1 or /categories/cat2 or /categories/cat3 or /categories/cat4 or /categories/cat5 or /categories/cat6 otherwise NotFound component appears.
function App() {
return (
<Router>
<Header />
<Container >
<Switch>
<Route exact path='/' component={Home} />
<Route path='/categories/(cat1|cat2|cat3|cat4|cat5|cat6)' component={ItemList} />
<Route path="*" component={NotFound} />
</Switch>
</Container>
<Footer />
</Router>
);
}
I want to use dynamic array for ItemList routing like this:
const itemCategories = ['cat1','cat2','cat3','cat4','cat5','cat6'];
in this path:
path='/categories/(cat1|cat2|cat3|cat4|cat5|cat6)'

If I understand your question you want to render a route that handles multiple categories.
Render a single path with a match parameter. This allows you to dynamically handle any category value.
<Route path='/categories/:category' component={itemList} />
Render a single route with a path array. This allows you to handle specifically allowed categories. This results in path={["/categories/cat1", "/categories/cat2", ... , "/categories/cat6"]}.
<Route
path={itemCategories.map(cat => `/categories/${cat}`)}
component={itemList}
/>
Render a single route with a path array and map in the regex. This results in path='/categories/(cat1|cat2|cat3|cat4|cat5|cat6)' as you were looking for.
<Route
path={`/categories/(${itemCategories.map((cat) => cat).join("|")})`}
component={ItemList}
/>
Render a route for each category. This allows you to handle specifically allowed categories but basically duplicates the Route component.
{
itemCategories.map(cat => (
<Route key={cat} path={`/categories/${cat}`} component={itemList} />
))
}
I want to route just for cat1 to cat6. If user types something else
like categories/cat7 notFound page will appear.
For this I would suggest option 2 or 3.

You can use : to set it to a dynamic route like this:
<Route path="/categories/:cat" component={itemList} />
and then render the route according to the users choice
Or you can map the routes like this:
{itemCategories.map(route => {
return <Route path=`/categories/${route}}` component={itemList}/>
}}
I'm not sure what you want so I gave both options

Related

Create inverse react-router <Route /> path; hide on match

I would like a way to invert a Route by path, instead of include like the standard behavior.
Instead of doing:
<DefaultRoute path={['/home', '/contact']} component={NavbarButtons} /> <-- Keep adding for each new page
<DefaultRoute path={'/home'} component={Home} />
<DefaultRoute path={'/contact'} component={Contact} />
<DefaultRoute path={'/no-nav-page'} component={NoNavPage} />
I'd like to achieve this:
<DefaultRoute path={"*"} hidePath={['/no-nav-page']} component={NavbarButtons} />
<DefaultRoute path={'/home'} component={Home} />
<DefaultRoute path={'/contact'} component={Contact} />
<DefaultRoute path={'/no-nav-page'} component={NoNavPage} />
This becomes especially useful when you keep adding new pages, but want to include them in the default set of pages when the NavbarButtons show up, and not manually have to add a path definition for each new page.
I could use useLocation to match the path manually and conditionally render a component:
const { pathname } = useLocation()
return (
<>
{routeVars.hideTopbar.includes(pathname) ? null : <TopBar />}
</>
)
But that becomes overcomplicated when using paths like "/page/:id".
The initial way of specifying an inclusive set of routes you want the NavbarButtons component to render with is the standard conventional way of conditionally rendering routed content, but if you wanted to do the inverse you would need a route that unconditionally matched anything and then explicitly checked a set of routes to exclusively render content on.
The following example uses the useRouteMatch hook to match a set of "hidepath" paths. If there is a match then the custom InverseRoute component returns null to "hide" the routed content, otherwise it returns a regular Route with the regular route props passed through. This pattern is just a specialized route protection implementation.
Example:
import { Route, useRouteMatch } from "react-router-dom";
const InverseRoute = ({ hidePath, ...props }) => {
const hide = useRouteMatch(hidePath);
return hide ? null : <Route {...props} />;
};
<InverseRoute
path="*" // <-- match anything
hidePath={["/no-nav-page"]} // <-- hide on these matches
component={NavbarButtons}
/>
<Switch>
<Route path="/home" component={Home} />
<Route path="/contact" component={Contact} />
<Route path="/no-nav-page" component={NoNavPage} />
</Switch>

Render React page dynamically using URL parameter

I've got a page at <url>/machines which lists the IP addresses of a set of machines on a network. I want to be able to click on one in the list and link to a page <url>/machines/<machineid> to render a new page which which show information about that specific machine. I want the value specified in the URL as <machineid> to be passed into the rendered page as a usable value, e.g. in a prop/param etc.
I'm having trouble configuring react router to achieve this, and am wondering if anyone can see what I'm doing wrong? I've been following the React Router V6 docs, however can't seem to get it to work. When I render the page at <url>/machines/hello, I get a console error saying No routes matched location "/machines/hello". Can anyone see what I'm doing wrong?
I was initially thinking I'd just render a new page (using a different component) to render the Machine Info page, however looking at the React Router V6 docs, it seems like the <MachineInfo> component is now rendered as a child of <Machines>?
I have an alert() in the <MachineInfo> component which doesn't seem to be being run at all. I get no alert.
App.js
function App() {
const value = useContext(Context);
return (
<div className="App">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="machines" element={<Machines />}>
<Route path="machines/:id" element={<MachineInfo />} /> // I've tried this using just path=":id" as well with no luck
</Route>
<Route path="topology" element={<Topology />} />
<Route path="settings" element={<Settings />} />
</Routes>
</div>
);
}
MachineInfo.js
export default function MachineInfo(props) {
const [state, dispatch] = useContext(Context);
let { id } = useParams<"id">([]);
alert("info: " + id)
return (
<p>hello</p>
);
}
First, you'll want a Layout component that will have your Outlet
export function Layout() {
return (
<>
<Outlet />
</>
);
Now wrap your other routes within this Layout component & you'll notice you can now get to your nested route by saying /machines/1
The Dashboard component is the index so / should match this route.
function App() {
// Not sure why you have value since it doesn't seem to be getting used
const value = useContext(Context);
return (
<div className="App">
<Routes>
<Route path="/*" element={<Layout />}>
<Route index element={<Dashboard />} />
<Route path="machines" element={<Machines />}>
<Route path=":id" element={<MachineInfo />} />
</Route>
<Route path="topology" element={<Topology />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</div>
);
}

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.

Prioritise nested route over main route

Setup
I have an App component rendering following routes:
<Route path="/items/:id" component={ItemDetail} />
<Route path="/items" component={AllItems} />
In my AllItems component I render a list of all items and the option to create a new item or update an existing one. Doing either one of those actions opens a popup. To do this I render following routes in AllItems:
<Route path="/items/add" component={AddItemModal} />
<Route path="/items/edit" component={EditItemModal} />
Note: It's important that these modals are actually linked to these routes, I can't change that. Neither can I render those routes outside of AllItems as I need to pass soms props to the modals.
Problem
When I go to a route like /items/1: ItemDetail renders (as expected).
When I go to /items/add: ItemDetail renders with add as :id.
I need it to render AddItemModal here as defined in AllItems.
What I tried:
I tried adding exact to the /items/:id route and I also tried adding it to /items/add & /items/edit. Neither of those solutions worked. Either only ItemDetail rendered, or only the modals.
I tried defining /items before /items/:id to hopefully give higher priority to the nested routes. ItemDetail never rendered in this case.
Is there a solution to this so I can prioritise items/add & items/edit over items/:id
Try nesting the routes under /items
<Route
path="/items"
render={() => (
<>
<Route path="" component={AllItems} exact />
<Route path="/add" component={AddItemModal} />
<Route path="/edit" component={EditItemModal} />
<Route path="/:id" component={ItemDetail} />
</>
)}
/>
If you want to have an independent views for ItemDetail and AllItems but at the same time have /items/add and /items/:id/edit (took a little liberty with the url, you need and id to edit an item right?) as modals over AllItems so the structure of the routes would be something like this:
AllItemsView (/items)
AddItemModal (/items/new)
EditItemModal (/items/:id/edit)
ItemDetailView (/items/:id)
You need a little tweak of Tnc Andrei response:
<Route
path="/items"
render={({ match: {url, isExact}, location: {pathname} }) => {
let pathnameArray = pathname.split("/")
let lastChunk = pathnameArray[pathnameArray.length - 1]
if (isExact || lastChunk === "new" || lastChunk === "edit") {
return (
<>
<Route path={`${url}/`} component={CompetitionsView} />
<Switch>
<Route path={`${url}/new`} component={CompetitionFormModal} />
<Route path={`${url}/:competitionId/edit`} component={CompetitionFormModal} />
</Switch>
</>
)
}
return (
<>
<Route path={`${url}/:competitionId`} component={CompetitionView} />
</>
)
}}
/>

Nested routes and Switch - how to pass props.match.params.id?

I have been trying to understand nested routes and switch in the React v4 Router.
Consider the main router looks like this (simplified):
<Switch>
<Route path="/" component={LoginPage} exact={true} />
<Route path="/dashboard/edit/:id" component={DashboardPage} />
<Route path="/dashboard" component={DashboardPage} />
</Switch>
The "dashboard" component renders the sub-route:
render(){
return (
<div className="note">
<Route to='/edit/:id' render={(props) =>
<div>
<NoteList {...props} />
<EditNotePage {...props} />
</div>
} />
</div>
)
}
The "EditNotePage" component can access the param by:
const mapStateToProps = (state, props) => ({
note: state.notes.find((note) => note.id === props.match.params.id
});
Is this the correct approach?
It seems a little redundant to specify "/dashboard/edit/:id" twice ( ? )
Once in main router and the again in the dashboard component.
However, if I do not match the route in the main router "Switch" the "props.match.params.id" is not accessible since "props.match" will only point to "/dashboard" .
Have I missed something crucial regarding how the React v4 Router works? :)
Kind regards
Kermit
Nope, didn't miss anything. That's how react router v4 works. You define full routes. The trick you can use is that you can grab the current path and prepend it to your "nested path".

Categories