Prevent an user from accessing other routes - ReactJS - React-Router - - reactjs

I have two environments: 'Administrador' and 'Alumno'. I would like to know how I can do so such that an 'Administrador' user can not access the routes of an 'Alumno' user and vice versa. I am using React-Router 2.4.0. Is this possible with this technology? I am new to ReactJS (I use version 15.4.2).
Another question: Would it be convenient to update to the new version and transpile all my routes?
Here are my routes:
<Router history={browserHistory}>
<Route path="/" component={NotFound}/>
<Redirect from="/alumno" to="/alumno/inicio"/>
<Redirect from="/administrador" to="/administrador/inicio"/>
<Route path="/" component={App}>
<Route path="alumno" component={AppAlumno}>
<Route path="inicio" component={Alumno_Inicio}/>
<Route path="nueva_incidencia" component={Alumno_NuevaIncidencia}/>
<Route path="mis_incidencias" component={Alumno_MisIncidencias}/>
</Route>
<Route path="administrador" component={AppAdministrador}>
<Route path="inicio" component={Administrador_Inicio}/>
<Route path="nueva_incidencia" component={Administrador_NuevaIncidencia}/>
<Route path="incidencias_recibidas" component={Administrador_IncidenciasRecibidas}/>
<Route path="incidencias_recibidas/nuevo_informe" component={Administrador_NuevoInforme}/>
<Route path="informes" component={Administrador_Informes}/>
<Route path="informes/nueva_respuesta_informe" component={Administrador_NuevaRespuestaInforme}/>
</Route>
</Route>
</Router>
Thank you.

So to answer your questions:
For the first question:
Yes, limiting access to routes is possible using this technology. There are several ways to do it, the simplest way to do it is to check for identity of the user in the componentWillMount() function. It may look something like this, depending on how you identify your user as an Administrador or an Alumno:
import React from 'react';
import { browserHistory } from 'react-router';
class YourComponent extends React.Component{
componentWillMount(){
//If user is an Alumno, throw them to '/some/path'
if(this.state.isAlumno)
browserHistory.push('/some/path');
}
render(){
return <YourJsx />;
}
}
export default YourComponent;
This way you don't need to change your routes.
You can also achieve the same functionality by having a higher order component (HOC) that returns the component you want to limit access to. If you want to go the HOC way, you first need to create a new component that may look like this:
import React from 'react';
import {browserHistory} from 'react-router';
//This function receives the Component that only some user should access
function RequireAdmin(ComposedComponent){
class Authenticated extends React.Component {
componentWillMount(){
if(this.state.isAlumno)
browserHistory.push('/some/path');
}
render(){
return <ComposedComponent />;
}
}
//Return the new Component that requires authorization
return Authenticated;
}
Then, the route that you want to limit access to would look like this:
<Route path="administrador" component={RequireAdmin(AppAdministrador)}>
...
</Route>
Personally, I prefer the second way. It makes it clearer on the route which routes require what kind of authorization, and separates the components from the authorization.
To answer the second question:
It depends on how much of a hassle it would be. I'd certainly recommend learning the new API react-router v4, but I don't think it would be worth it to update an already built project, specially one with as many routes as yours.
Hope this helps! Buena suerte con React.

Related

Proper place to setup React Router history listener

I'm trying to set up a history listener at the topmost level so that every time the location changes, some code is executed (Google Analytics tracking in my case).
Currently, my App.js looks like this:
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Home}/>
<Route path="/" component={MainLayout}/>
</Switch>
</BrowserRouter>
);
}
As I understand, I can only use history inside BrowserRouter, but it can only have one child, so I can only put it inside Switch, but then I'll have to put it inside every component under Switch, which I can't do since I want it to run only once. If I just add a custom component under Switch without wrapping it into a Router, there's no history.
What should I change to make it work? Is it possible to get an instance of history in the App.js code? Is it possible to achieve using a custom component or a HOC?
You need to add a Route which will get rendered unconditionally.
Try something like this:
render() {
return (
<BrowserRouter>
<React.Fragment>
<Route component={HistoryListenerComponent}/>
<Switch>
<Route path="/" exact component={Home}/>
<Route path="/" component={MainLayout}/>
</Switch>
</React.Fragment>
</BrowserRouter>
);
}
So HistoryListenerComponent is always rendered and you can listen from its componentDidMount.
Heres what worked for me:
import {withRouter} from 'react-router-dom';
Now just wrap the component where you are using React-ga with withRouter like this:
componentDidMount=()=>{
ReactGa.initialize('00000'); //enter id
ReactGa.pageview(window.location.pathname + window.location.search);
this.props.history.listen(location => {
ReactGa.set({ page: location.pathname }); // Update the user's current page
ReactGa.pageview(location.pathname); // Record a pageview for the given page
});
}
render (){
return (
<Switch>
<Route ....>
.....
<Switch>
);
}
export default withRouter(ComponentName)
Remember without wrapping withRouter , you cant use this.props.history property.
You cant use custom history listener to BrowserRouter component. It has an inbuilt history prop (i.e this.props.history).
It is possible using history package, instead of relying on the default one created by react router.
For instance, using history.listen:
history.listen((location) => {
ReactGA.pageview(location.pathname + window.location.search);
});
In my exemple, I use react-ga, as a way to standardize our GA Instrumentation across projects.
Found out that the easiest option is the Update component -- https://github.com/pshrmn/rrc/blob/master/docs/OnUpdate.md

Multilang routes with React Router V4 and redirects

So I have a React app wich is using Redux and I need to set up I18n on it. I decided to use the react-redux-i18n package (not sure that's the best idea, but the alternatives like yahoo's react-intl or even react-intl-redux seem to be way too complicated). The initial setup went smootly, but now I got into a roadblock.
I want to pass the locale as a part of the requested url. E.g. /en/ for root path, /en/forums for forums, etc. Wich means that for every path I need 3 routes:
/en/:path for the English (default) locale
/ru/:path for the Russian one
/:path to be a <Redirect> to the default locale
Writing 3 routes for every path seems way too dirty and uncomfortable so I decided to make a reusable component for it:
import React from 'react';
import { Redirect } from 'react-router-dom';
import LayoutRoute from './LayoutRoute';
import appConfig from './config/appConfig';
class I18nRoute extends React.Component {
render() {
const { path, ...rest } = this.props;
return (
<div>
<Redirect from={path} to={`/${appConfig.DEFAULT_LOCALE}${path}`}/>
<LayoutRoute exact path={`/en?${path}`} lang='en' {...rest} />
<LayoutRoute exact path={`/ru?${path}`} lang='ru' {...rest} />
</div>
)
}
};
export default I18nRoute;
and then tried using it in my router setup
<Switch>
<I18nRoute exact path="/" layout={MainLayout} component={Home} />
<I18nRoute exact path="/forums" layout={MainLayout} component={ForumsRoot} />
<I18nRoute exact path="/forums/:id" layout={MainLayout} component={Forum} />
<I18nRoute exact path="/topics/:id" layout={MainLayout} component={Topic} />
<I18nRoute exact path="/users/:id" layout={MainLayout} component={UsersPage} />
</Switch>
That backfired in two ways:
It doesn't recognize the localed paths, even /en/ leads to route not found.
It doesn't redirect with params in, e.g. /forums/18 gets patched to /en/forums/:id.
is there some fix to this or do I need to go a completely different route? (pun intended)
UPD
After carefully writing this down I realized that the redirect rule could be simplified to a regex (if the route does not match /^/(en|ru)/(.*)$/ then redirect to the /en/:path).

Redux + React : react router only rendering index route

I have tried following the answer on this question but to no avail.
I am trying to use react router and nested routes to render different layouts depending on the configuration of the router.
But the route with path="/" always shows regardless of the URL that is present.
Here is the router that I am using.
<Route component={App}>
<Route component={LayoutOne}>
<Route path="/" component={ComponentOne}/>
</Route>
<Route component={LayoutTwo}>
<Route path="category" component={ComponentTwo}/>
</Route>
</Route>
And here is the App file that I use to map the dispatch to the props, and connect.
function mapStateToProps(state, ownProps){
return{
exampleProp: state.exampleProp,
};
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(MainLayout);
export default App;
Here is the main layout MainLayout as seen above.
export default class MainLayout extends Component {
render(){
<div className="main-layout">
{this.props.children}
</div>
}
}
LayoutOne and LayoutTwo(has been omitted for brevity) are simple class rappers as follows.
export default class LayoutOne extends Component{
render(){
return(
{this.props.children}
)
}
}
And here the ComponentOne and ComponentTwo respectively are just simple components.
My problem is that only ComponentOne renders regardless of what URL is present.
How do I fix this issue so that I could use nested routes as shown above?
Your help would be much appreciated.
If you need any additional information, please ask and I will do my best to update the question with the required information.
Thanks.
The problem you're having is that the index route, i.e. '/', is being matched first in your route tree, and because it matches any route, it is the route that gets rendered every time.
You can fix this by moving the Route with path="category" above the one with path="/". This way the more detailed route option gets checked first. I would also recommend changing path="category" to path="/category" just for clarity's sake as both are root level routes.
Changing your routes to look like this should fix your problem
<Route component={App} path="/">
<Route component={LayoutOne} path="">
<Route component={ComponentOne}/>
</Route>
<Route component={LayoutTwo} path="category">
<Route component={ComponentTwo}/>
</Route>
</Route>

foward to a specific url with react-router

In my React-router-redux-redial application, we can use two url :
/accountId
/accountId/language
The language is optional.
When the language param is not set, we have to force the "fr" language.
Is there any possibility with react-router to foward automatically "/accountId" to "/accountId/fr" ?
The IndexRoute doesn't have any path attribut to force the url and the redirect function does not fit our needs, we want to see "/accountId/fr" in the url for some SEO reasons.
I tried to make a dispatch(push("/accountId/fr")) in the render function of my LandingCmp but it not works.
We use Redial (#provideHooks) to ensure a clean pre-render on server side.
Here is my current routing table :
<Route path='/'>
<Route path="error" component={ErrorsCmp} />
<Route path=":accountId">
<Route path=":langue">
<IndexRoute component={LandingCmp} />
</Route>
<IndexRoute component={LandingCmp} />
</Route>
</Route>
We use the following dependencies :
- "react-router-redux": "4.0.5"
- "react-router": "2.7.0"
You can import browserHistory and then use that to push to different routes dynamically. So you can check the params to find out if there is any parameter after accountId, if there isn't you can use:
browserHistory.push("/accountId/fr");
And to import browserHistory:
import {browserHistory} from 'react-router';
You can push url in the componentWillMount lifecycle method using the withRouter wrapper which is available in the newer versions of React Router (since version 2.4.0). More info: https://github.com/ReactTraining/react-router/blob/master/docs/API.md#withroutercomponent-options
The structure may look like as follows:
import { withRouter } from 'react-router';
class App extends React.Component {
componentWillMount() {
this.props.router.push('/accountId/fr');
}
render() {
// your content
}
}
export default withRouter(App);

Using two independent paths in React Router

I am currently using React Router and have routes that use the browserHistory, for example:
import { Router, Route, IndexRedirect, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="tasks" component={Tasks}>
<IndexRoute component={List} />
<Route path="/:id" component={Item} />
</Route>
</Router>
Which allows me to do routes such as:
/tasks/
/tasks/1234
This works, but we have come across a situation where we have two views that are displayed at the same time. We'd like for the link to be shareable and have the app open with both views.
So for example, if we have tasks on the left side of the screen, and a shop on the right, we'd like for there to be two independent parts of the path, something like:
/tasks/1234/#/shop/item/xyz
The shop route should be independent of the left of the hash, and the tasks route should be independent of the right of the hash, so that /new-feature/xyz/#/shop/item/xyz should still render the same view on the right side of the window.
Is it possible to have React Router do routes like this? Or will I have to write a custom router to solve this?
I'm guessing I'd basically have to combine the browserHistory and hashHistory together, but I don't think that's possible with React Router out of the box.
I think rolling your own router just to handle this case might be a little overboard. You can have as many different paths in your route config as you want, and access param information in your components:
<Router history={browserHistory}>
<Route path="/" component={App} />
<Route path="/tasks/:taskId" component={App} />
<Route path="/shop/:shopId" component={App} />
<Route path="/tasks/:taskId/shop/:shopId" component={App} />
</Router>
let App = ({ params }) => // simplified
<div>
{ params.shopId && <Shop /> }
{ params.taskId && <List /> }
</div>
Just a thought.. I think there are several ways to augment this to handle more complex scenarios but this would definitely work for what you've specified in the question (for the most part).
Update: Above is 'hardcoded', but of course you do not need to write out every combination by hand. This is what loops are for.
import * as routeComponents from './routeComponents'
<Router history={browserHistory}>
{ routeComponents.map(r => <Route {...r} />) }
</Router>
let App = ({ params }) =>
<div>
{ routeComponents.reduce((matches, r) => ([
...components,
...(params[r.slug] ? [r.component] : [])
]), [])}
</div>
We have developed our own router Boring Router with "parallel routing" support, though it depends on MobX and works differently with React Router in many ways.
In Boring Router, a parallel route is stored as query string parameter prefixed with _. E.g., /app/workbench?_sidebar=/achievements.
Check out a complete example here.

Resources