Create routes from a set of values - reactjs

I would like to set the routes for the languages my webpage accepts, something like
import Component from '../src/component/component';
<Switch>
<Route exact path="/es" component={(props) => <Component language="es" />}/>
<Route exact path="/en" component={ (props) => <Component language="en" />}/>
</Switch>
But the languages that are accepted depend on a configuration file, and more can be added in the future. So I would like to be able to add these routes depending on the values of this file.
The option I've seen is to add the routes like this:
}/>
But don't want to accept any possibility, just some the ones I need.
Is there a way to create routes to accept several possible routes but not any route?
thanks.

You can write a simple mapping for the accepted languages and then loop over it to render the Routes
const languages = ['es', 'en'];
import Component from '../src/component/component';
...
<Switch>
{languages.map(lang => {
return <Route
key={lang}
exact
path={`/${lang}`}
component={(props) => <Component {...props} language={lang} />}/>
})}
</Switch>

The Switch-component in react router will render the first component that does not return null, so we can hijack this behavior to accomplish what you need.
First, we define an object mapping languages to components:
const langs = {
en: ComponentEn,
es: ComponentEs,
};
Then we can define a language selector component:
const LanguageSelector=({match})=>{
const language = match.params.language
const Component = langs[language];
if(Component) return <Component/>
else return null;
}
Now we can use it in our routing
<Switch>
<Route path="/:language" component={LanguageSelector}/>
<DefaultComponent/>
</Switch>
Of course you can replace the DefaultComponent with whatever you want

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>
</>
);
};

How can I put a route like "/items?Search="?

I have been assigned a task where I must put a route with the following url: /items?search= to load SearchBarScreen component.
I have this code and I am using react-router-dom package:
export const AppRouter = () => {
return (
<Router>
<Switch>
<Route exact path="/items?search=" component={SearchBarScreen} />
<Route exact path="/product/:id" component={ProductDetailScreen} />
<Route path="/" component={HomeScreen} />
<Route component={NotFoundScreen} />
</Switch>
</Router>
);
I can't load my SearchBarScreen component with the url:
http://localhost:3000/items?search=XDDD
the route HomeScreen (/) is loaded in this case :(
what am I doing wrong?
You should feed the URI without the query string to the Route component. So if you want to render your SearchBarScreen at this URL http://localhost:3000/items?search=, you should do this:
<Route exact path="/items" component={SearchBarScreen} />
And then access the query string inside SearchBarScreen like so:
const SearchBarScreen = ({ match, location }) => {
console.log(location.search);
return <div>Search results</div>;
};
Another way is to drop the exact prop.
My recommended solution is the former. Here is a Sandbox: https://codesandbox.io/s/elated-http-lkqfj?file=/src/App.js
The exact param is used when you have multiple paths that have similar names:
for exmplae in your code when you go to //localhost:3000/items?search=XDDD the router will go through all of our defined routes and return the FIRST match it finds.And this is the first thing it finds //localhost:3000/ And executes the HomeScreen component.
The exact param disables the partial matching for a route and makes sure that it only returns the route if the path is an EXACT match to the current url.
So in this case,you should add exact to / route:
<Router>
<Switch>
<Route path="/items?search=" component={SearchBarScreen} />
<Route path="/product/:id" component={ProductDetailScreen} />
<Route exact path="/" component={HomeScreen} />
<Route component={NotFoundScreen} />
</Switch>
</Router>
You can do something like this
You can use this custom hook
useLocation hook is imported from react-router-dom
useQuery() {
return new URLSearchParams(useLocation().search);
}
Then in component
const query = useQuery();
Docs Query param example

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>

How to set React.useState based on URL

I am trying to set the following to different numbers based on the page the user is on using window.location.path
const [value, setValue] = React.useState(1);
I have tried if statement and I seem to get errors so my thinking was to set a params to
<Route exact path="/" component={Home} />
however while I understand I could do something like
<Route exact path="/:id" component={Home} />
the issue is the following as you can see below I have a number of set paths, the tabs in the nav bar are linked to the main path (/, /about, /news, /programs etc)
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about/" component={About} />
<Route path="/news/:id" component={News} />
<Route path="/programs/:id" component={Programs} />
<Route path="/podcast/:id" component={Podcast} />
</Switch>
I want to be able to send a number with each Route so that way I can just set the following with something like
const [value, setValue] = React.useState({pageID});
I was thinking something like
<Route path="/podcast/:id" pageID="4" component={Podcast} />
How can I do this?
let say that you are using this in component News
The first step to be able to extract the param out of the link is using withRouter
Import it in your component News file: import { withRouter } from "react-router";
We need to wrap the component inside the withRouter HOC as follows:
//instead of exporting it as export default News.
export default withRouter(News) //this will make the API we need available at the component's props
supposing you want to do that:
const [value, setValue] = React.useState({pageID});
at the component mount, inside componentDidMount we can extract the param as follows:
const { id } = this.props.match.params //the id the user navigated to: /news/:id
and then you can use the id to update your state.
You can try to pass parameters to component with render method.
<Switch>
<Route exact path="/" render={(props) => <Home pageId=1 {...props} />} />
<Route path="/about/" render={(props) => <About pageId=2 {...props} />} />
</Switch>
You can add multiple params to the path as -
path="/podcast/:id/:pageId"
And to retrieve -
this.props.match.params.pageId

React Router - how to constrain params in route matching?

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.

Resources