React Router - how to constrain params in route matching? - reactjs

I don't really get how to constrain params with, for example a regex.
How to differentiate these two routes?
<Router>
<Route path="/:alpha_index" component={Child1} />
<Route path="/:numeric_index" component={Child2} />
</Router>
And prevent "/123" from firing the first route?

React-router v4 now allows you to use regexes to match params -- https://reacttraining.com/react-router/web/api/Route/path-string
const NumberRoute = () => <div>Number Route</div>;
const StringRoute = () => <div>String Route</div>;
<Router>
<Switch>
<Route exact path="/foo/:id(\\d+)" component={NumberRoute}/>
<Route exact path="/foo/:path(\\w+)" component={StringRoute}/>
</Switch>
</Router>
More info:
https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#custom-match-parameters

I'm not sure if this is possible with React router at the moment. However there's a simple solution to your problem. Just do the int/alpha check in another component, like this:
<Router>
<Route path="/:index" component={Child0} />
</Router>
const Child0 = (props) => {
let n = props.params.index;
if (!isNumeric(n)) {
return <Child1 />;
} else {
return <Child2 />;
}
}
* Note that the code above does not run, it's just there to show what I mean.

Related

Which PrivateRouter realization is better: higher-order component or substitution?

So recently I found out two ways of creating private routes in react.
With a HOC (higher-order component):
const PrivateRoute = ({ user, children }) => {
if (!user) {
return <Navigate to="/home" replace />;
}
return children;
};
const App = () => {
...
return (
<>
...
<Routes>
<Route path="/home" element={<Home />} />
<Route
path="/privateroute"
element={
<PrivateRoute user={user}>
<PrivateComponent />
</PrivateRoute >
}
/>
...
</Routes>
</>
);
};
With substituting routes completely
const App = () => {
...
return (
<>
{user ? (
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/privateroute" element={<PrivateComponent />} />
...
</Routes>
) : (
<Routes>
<Route path="/home" element={<Home />} />
...
</Routes>
)}
</>
);
}
My fellow colleague told me that the second way is quite bad since it completely erases some routes (if user is falsy then there is no route to /privateroute). But on my question why might that be bad he had no definitive answer. I couldn't find anything on the internet either. Any thoughts on which way is the best?
Between these two options, the first is the preferred solution since it keeps all routes mounted so they there will be no race condition between setting the user state and issuing an imperative navigation action to one of the protected routes. In other words, with the second implementation you have to wait for the user state to update and trigger a component rerender so the protected routes are mounted and available to be navigated to.
The second method also duplicates unauthenticated routes if it's all one or the other. Code duplication should be avoided.
Note however though that the first example isn't a Higher Order Component, it's just a wrapper component.
Note also that it's more common to create a PrivateRoute component as a Layout Route instead of as a Wrapper component. The change is trivial but it makes the component a little more wieldy. Render an Outlet component for nested routes instead of the children prop for a single wrapped child component.
import { ..., Outlet } from 'react-router-dom';
const PrivateRoute = ({ user }) => {
return user ? <Outlet /> : <Navigate to="/home" replace />;
};
Now instead of wrapping each individual route you want to protect you render a layout route that wraps an entire group of routes you want to protect. It makes your code more DRY.
const App = () => {
...
return (
<>
...
<Routes>
<Route path="/home" element={<Home />} />
... other unprotected routes ...
<Route element={<PrivateRoute />}>
<Route path="/privateroute" element={<PrivateComponent />} />
... other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
</>
);
};

Split react router keeps matching 404 route on all routes

Split my routes into two (for now, going to be four later) separate components.
const RouterComponentOne = () => {
return (
<Switch>
<Route path="/routeA" component={ComponentA} />
<Route path="/routeB" component={ComponentB} />
</Switch>
)
}
const RouterComponentTwo = () => {
return (
<Switch>
<Route path="/routeC" component={ComponentA} />
<Route component={Component404} />
</Switch>
)
}
const RouterDefault = () => {
return (
<>
<RouterComponentOne />
<RouterComponentTwo />
</>
)
}
export default RouterDefault;
When visiting any route from RouterComponentOne the 404 component is rendered (which it shouldn't). It renders on non-existent routes (example /test/123/abc which is correct, and doesn't render on any RouterComponentTwo route.
I added some dummy components to the RouterDefault and the 404 component rendered on all additional paths as well.
In short, the 404 route isn't matched/rendered within the routes of the component where the 404Route is. If put into the RouterComponentOne it won't render on routeA and routeB but will render for routeC
How can I solve this?
You need a property called exact
const RouterComponentOne = () => {
return (
<Switch>
<Route path="/routeA" exact component={ComponentA} />
<Route path="/routeB" exact component={ComponentB} />
</Switch>
)
}

Route parameters in react

I have many routes
<Route exact path="/" component={Test} />
<Route exact path="/1" component={Test1} />
<Route exact path="/2" component={Test2} />
In every component i use useLocation to get the data from route. It exists a possibility to pass in Route a parameter and to access that parameter with useLocation, in every component?
Here is the example:
<Route exact path="/" parameter='this is route nr 1' component={Test} />
<Route exact path="2/" parameter='this is route nr 2' component={Test2} />
Which is the solution to do what i want to achieve?
For query params you don't need to do anything extra in <Route />. You just need to get those query params in component and parse it
To access param1 from url
/?param1=hello
import { useLocation } from 'react-router-dom';
const Test = () => {
const queryParams = new URLSearchParams(useLocation().search);
return (
<div>Test {queryParams.get('param1')}</div>
);
}
And if you want path params like
/1 // 1 from this route
In routes and you don't need to create multiple routes just to get 1,2 etc
<Route exact path="/:id" component={Test} />
In component
import { useParams } from 'react-router-dom';
const Test = () => {
let { id } = useParams();
return (
<div>Test ID: {id}</div>
);
}
It seems from your question that you search for a way to pass data to routes only at the router declaration. therefore you can use regular props instead of location data extract -
<Route exact path="/" render={()=><Test parameter={'this is route nr 1'} />} />
You can either pass props to the component directly using render:
<Route
exact
path="/"
render={props => <MyComponent {...props} foo="hello world" bar={false} /> }
/>
or you can use query params:
<Route
exact
path={`/user?id=${user_id}`}
component={MyComponent}
/>
and then in MyComponent you can access props.match.params.id
React-router docs are a great start

Split up routes in same router with reach-router

Say I have a lot of routes and I would like to split them up in groups.
How would I accomplish this in React with Reach Router?
Example of what I'm basically trying to accomplish:
const Router = () => {
return (
<Router>
<AnonymousRoutes />
<SecureRoutes />
</Router>
);
};
const AnonymousRoutes = () => {
return (
<>
<Page1 path="1" />
<Page2 path="2" />
</>
);
};
const SecureRoutes = () => {
return (
<>
<Page3 path="3" />
<Page4 path="4" />
</>
);
};
Edit: So, I based my answer off of a misreading of your problem statement. I thought I read react-router, not reach router. I apologize.
So using a fragment is exactly what you probably SHOULD be doing. However, React Reach doesn't currently support fragments. Silver lining, it looks like it will soon!
https://github.com/reach/router/pull/289
If I'm understanding your question correctly, I think what you're looking for is Switch from react-router.
The switch component allows a developer to segment out their routes and render specific content on the path.
It might look something like this:
import { Switch, Route } from 'react-router'
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>

Can we include normal react component inside <Route />?

I want to do something like:
<Route>
<MyComponent someCondition={true/false}>
<Route1 />
....
</MyComponent>
</Route
To handle some conditional rendering. However, <MyComponent /> seems not mounted upon rendering.
My question is: can we include normal react component within <Route>? If not, is there a better way to handle conditional routing?
What exactly do you mean by conditional routing? Assuming you mean something like not letting a user hit a route if they aren't authenticated, you can use react-router's onEnter hooks . You can make a parent <Route> that doesn't have a component prop and just handles routing checks. I used some simple onEnter checks in this example.
// onEnter hooks for login and home page to redirect if necessary
const checkAuth = function (nextState, replace) {
const { user } = store.getState()
if (isEmpty(user)) {
replace('/')
}
}
const checkSkipAuth = function (nextState, replace) {
const { user } = store.getState()
if (!isEmpty(user)) {
replace('/home')
}
}
var Index = () => {
return (
<Provider store={store}>
<Router history={history}>
<Route path='/' component={Container}>
<IndexRoute component={Login} onEnter={checkSkipAuth} />
<Route path='home' component={Home} onEnter={checkAuth} />
<Route path='*' component={NoMatch} />
</Route>
</Router>
</Provider>
)
}

Resources