Redirecting user based on state in React - javascript

Right now, my router looks like this:
<Router history={browserHistory}>
<Route component={Provider}>
<Route path="/" component={AppPage}>
<Route path="login" component={LoginPage}/>
<Route component={LoggedinPage}>
<Route path="onboarding" component={OnboardingPage}/>
<Route component={OnboardedPage}>
<IndexRoute component={HomePage}/>
<Route path="settings" component={SettingsPage}/>
</Route>
</Route>
</Route>
</Route>
</Router>
LoggedinPage redirects to /login if the user isn't logged in and OnboardedPage redirects to /onboarding if the user hasn't completed the onboarding flow (choosing username, etc). However, I think more of these conditional redirects may be needed in the future. What's the best way to handle these conditional redirects? Is it possible to have a single component that handles all the redirects?

<Route>s accept an onEnter hook that is called when the route matches. A hook would look something like this:
function requireAuth(nextState, replace) {
if (!loggedIn()) {
replace({ pathname: 'login' });
}
}
Then use it like so:
<Route path="/" component={AppPage}>
<Route path="home" component={HomePage} onEnter={requireAuth}/>
<Route path="login" component={LoginPage}/>
</Route>
A more composable example, that lets you combine multiple checks:
function checkAuth() {
if (!loggedIn()) {
return { pathname: 'login' };
}
}
function checkOnboarding() {
if (!completedOnboarding()) {
return { pathname: 'onboarding' };
}
}
function redirect(...checks) {
return function(nextState, replace) {
let result;
checks.some(check => {
result = check();
return !!result;
});
result && replace(result);
};
}
Then combine away!
<Route path="/" component={AppPage}>
<Route path="home" component={HomePage}
onEnter={redirect(checkAuth, checkOnboarding)}/>
<Route path="login" component={LoginPage}/>
<Route path="onboarding" component={OnboardingPage}
onEnter={redirect(checkAuth)}/>
</Route>

You could have a function that checks the if the user is logged in, and just import that:
import {browserLocation} from './browser-location'
export default function checkLoggedIn() {
if (localStorage.jwt === null)
browserLocation.pushState('/login')
}

Related

React-BreadCrumbs does not pick up variable from method

I have a multi-page web application running with reactjs.
I'm trying to define a custom react-breadcrumb for a specific page
which involves extracting a value and using that in the breadcrumb
Running the code with the below, I can see that console picks up jobName
But if I look at the breadcrumbs, I'm stuck with
home > templates > Missing name prop from Route
I don't quite understand why the variable isn't being picked up for the router. If I just hard code it in, it will work. Any advice?
getTemplateJobName(templateId,dateChosen){
doGetJobById({jobId: templateId,reconDate: dateChosen}).then(
({body: template})=>{
let {jobName: jobName}=template;
console.log(jobName);
return jobName;
});
},
render(){
return (
<div>
<Router history={browserHistory}>
<Route name='home' path={'/'+contextName}>
<IndexRoute component={LandingPage}/>
<Route name='templates' path='templates'>
<IndexRoute component={JobPage}/>
<Route path=':reconDate&:templateId' component={JobDetailPage} staticName={true} getDisplayName={(params) => this.getTemplateJobName(params.templateId,params.reconDate)}/>
</Route>
<Route name='report' path='report' component={ReportPanel}/>
</Route>
<Route path='*' component={NotFoundPage}/>
</Router>
Try this please;
export default App extends React.Component {
constructor(props) {
super(props);
this.state = {
DisplayName: ''
};
}
getTemplateJobName(templateId,dateChosen) {
doGetJobById({jobId: templateId,reconDate: dateChosen}).then(
({body: template})=>{
let {jobName: jobName}=template;
console.log(jobName);
// return jobName;
this.setState({ DisplayName: jobName });
}
);
}
getDisplayName(params) {
this.getTemplateJobName(params.templateId,params.reconDate);
return this.state.DisplayName;
}
render() {
return
(<div>
<Router history={browserHistory}>
<Route name='home' path={'/' + contextName}>
<IndexRoute component={LandingPage}/>
<Route name='templates' path='templates'>
<IndexRoute component={JobPage}/>
<Route path=':reconDate&:templateId' component={JobDetailPage} staticName={true} getDisplayName={ this.getDisplayName.bind(this) }/>
</Route>
<Route name='report' path='report' component={ReportPanel}/>
</Route>
<Route path='*' component={NotFoundPage}/>
</Router>
</div>)
}
}

React router get currently active segment as param

I have following router configuration
<Router history={history}>
<Route path="/" component={ReviewWizard}>
<IndexRoute component={Administrative}/>
<Route path="Administrative" component=Administrative}>
<Route path="/Administrative/:itemId" component={AdministrativeItem}/>
</Route>
<Route path="Offense" component={Offense}/>
</Route>
</Router>
I'm trying to get currently active route segment (ie Administrative or Offense).
Is there a way to do something like this? ie route constraints
<Router history={history}>
<Route path="/:segment" component={ReviewWizard}>
<IndexRoute component={Administrative}/>
<Route path="/:segment=Administrative/:itemId" component={Administrative}>
<Route path="/Administrative/:itemId" component={AdministrativeItem}/>
</Route>
<Route path="/:segment=Offense" component={Offense}/>
</Route>
</Router>
If not, what is best practice to get the current active route segment? I don't like this.context.router.routes[1].path
First off I would recommend the following router config, as it seems that it's what you're aiming for:
<Router history={history}>
<Route path="/" component={ReviewWizard}>
<!-- whenever we hit '/' we redirect to '/Administrative' -->
<IndexRedirect path="/Administrative"/>
<!-- Renders ReviewWizard <- Administrative -->
<Route path="/Administrative" component={Administrative}>
<!-- Renders ReviewWizard <- Administrative <- AdministrativeItem -->
<Route path="/Administrative/:itemId" component={AdministrativeItem}/>
</Route>
<!-- Renders ReviewWizard <- Offense -->
<Route path="/Offense" component={Offense}/>
</Route>
</Router>
As for detecting the currently active route (or if a route fragment is active), I would recommend using the router.isActive -method. Simply do something like this:
if (router.isActive('/Administrative')) {
doSomething()
} else if (router.isActive('/Offense')) {
doSomethingElse()
}
For something more declarative, I recommend just using the location object that react-router injects into each component it manages:
const { location: { pathname } } = this.props
const [ activeSegment ] = pathname.slice(1).split('/')
Hope these help!

Add authoriazation to react routers

I have defined routers in my react application. I have a 3 pages in my application. After the user successfully logs in, it is taken to the next screens. Flow is working fine. But there is a problem. When I directly enter the url for other pages in my application, it loads that page regardless of whether user logged in or not. I want to add a check on this. If user is not logged in then he must be redirected to the login page.
These are my routes
<Route path="/" component={LoginPage}/>
<Route path='app' component={App}>
<IndexRoute component={Home}/>
<Route path='/video-screen' component={VideoScreen}>
<IndexRoute component={TagList}/>
<Route path='/add' component={AddTags}/>
<Route path='/TagList' component={TagList}/>
<Redirect from='*' to='/'/>
</Route>
</Route>
</Router>
And this is my login component's method which checks the login credentials and take user to next page if login is successful
handleLoginButtonClick() {
var that = this;
let token;
var settings = {
"async": true,
"crossDomain": true,
"url": "https://www.backend.example.raccte.com/auth/login/",
"method": "POST",
"credentials": 'include',
"headers": {
"content-type": "application/x-www-form-urlencoded",
},
"data": {
"password": document.getElementById("password").value,
"username": document.getElementById("username").value
},
success:( response, textStatus, jQxhr )=> {
this.props.tokenAction(response.auth_token);
}
}
$.ajax(settings).done((response) => {
token = response.auth_token
console.log(token);
this.context.router.push('/app')
});
Updates
function authorize(){
if(window.localStorage.token == null){
browserHistory.push('/')
}
}
function getRoutes(store) {
return (
<Router>
<Route path="/" component={LoginPage}/>
<Route path='app' component={App} onEnter={this.authorize}>
<IndexRoute component={Home}/>
<Route path='/video-screen' component={VideoScreen}>
<IndexRoute component={TagList}/>
<Route path='/add' component={AddTags}/>
<Route path='/TagList' component={TagList}/>
<Redirect from='*' to='/'/>
</Route>
</Route>
</Router>
)
}
export default getRoutes;
gives me an error saying Uncaught ReferenceError: authorize is not defined
Routes have an onEnter functionality you can use for this. Let's say you have a function to authorize it inside the component containing the React router stuff. You could do something like this (some pseudo code here):
authorize() {
if (user is NOT authorized) browserHistory.push(login page)
}
<Route path="/" component={LoginPage}/>
<Route path='app' component={App} onEnter={this.authorize}/>
</Router>
That way even if they enter the URL straight into the browser URL bar, the onEnter function is still called, and if they aren't logged in it will redirect them to the login page.
Issue is u declared your authorize method in App component, you need to declare it in the file where you defined all routes, like this:
function authorize(){
console.log('hello');
}
<Router>
<Route path="/" component={LoginPage}/>
<Route path='app' component={App} onEnter={authorize}>
<IndexRoute component={Home}/>
<Route path='/video-screen' component={VideoScreen}>
<IndexRoute component={TagList}/>
<Route path='/add' component={AddTags}/>
<Route path='/TagList' component={TagList}/>
<Redirect from='*' to='/'/>
</Route>
</Route>
</Router>
I use Higher-Order Components for this check this exemple
RequireAuth
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
export default function (ComposedCmponent) {
class RequireAuth extends Component {
componentWillMount () {
//we need to check if user is authenticated before the component will mount
//here i check if the user is authenticated i'm using JWT authentification for this exemple, usually i use local storage to save the token and we can check the validation of the token
//If the user is not authenticated we redirect to /login
let validtoken = window.localStorage.getItem('id_token')
if (!this.props.isAuthenticated || !validtoken) {
this.context.router.push('/login')
}
}
componentWillUpdate (nexProps) {
// we need to the same as we did in componentWillMount
// in case component will update the use not authenticated
//If the user is not authenticated we redirect to /login
if (this.props.isAuthenticated !== nexProps.isAuthenticated) {
if (!nexProps.isAuthenticated) {
this.context.router.push('/login')
}
}
}
render () {
//now the user is authenticated we render the component passed by HOC component
return (
<ComposedCmponent {...this.props} />
)
}
}
and if i want secure a path i use my HOC RequireAuth in my router
<Route path='/' component={App}>
<Route path='/dashboard' component={requireAuth(Dashboard)} />
</Route>

React-router multiple component onEnter in case of auth?

I am creating one app that has 2 components for a single path. Here are my routes:
<Router history={hashHistory}>
<Route path="/" component={AppContainer} onEnter={requireEnter}>
<Route path="/homepage" component={HomePage} />
<Route component={MainPage} onEnter={requireAuth}>
<Route path="/home" component={DashBoard} />
</Route>
</Route>
</Router>
In this case I am entering into my app using the AppContainer component. Instead of that I want that component should be of my choice because I have a home page that will have path="/".
Right now onEnter={requireEnter} is handling in following cases:
function requireEnter (nextState, replace) {
if (nextState.location.pathname == "/") {
if (checkLoggedIn()) {
replace({
pathname: '/home'
})
} else {
replace({
pathname: '/homepage'
})
}
}
}
But I want that something like this:
function requireEnter (nextState, replace) {
if (nextState.location.pathname == "/") {
if (checkLoggedIn()) {
//component should be AppContainer and redirect to '/home'
} else {
//component should be home page
}
}
}
You can add a Route with no component in it, had used it in another similar case:
<Route path="/" onEnter={requireEnter}>
<Route path="home" component={AppContainer}>
<IndexRoute component={HomeContainer} />
</Route>
<Route path="homepage" component{HomePageContainer}>
</Route>
</Route>
Then as usual:
function requireEnter (nextState, replace){
if(nextState.location.pathname=="/"){
if(checkLoggedIn()){
replace({
pathname: '/home'
});
}else{
replace({
pathname: '/homepage'
});
}
}
}

Clean way to re-route user that isn't logged in with React?

I guess I could always add login in WillTransitionTo which would do a call to the store via Redux to verify that the user is logged in but this seems a bit hacky, especially since since I have to add WillTransitionTo() logic to every single component.
Anyone have any better ideas? I'm going to work toward wiring something into isAuthorized with the onEnter.
This is the root root of the application which performs the react-router wire-up.
react-router : 2.0.4
react: 15.0.2
const store = configureStore();
function isAuthorized() {
}
render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={Application} onEnter={isAuthorized}>
<IndexRoute component={DashboardPage} />
<Route path="login" component={LoginPage} />
<Route path="dashboard" component={DashboardPage} />
<Route path="items" component={DashboardPage} />
<Route path="item-categories" component={DashboardPage} />
<Route path="modifiers" component={DashboardPage} />
<Route path="taxes" component={DashboardPage} />
<Route path="discounts" component={DashboardPage} />
<Route path="orders" component={DashboardPage} />
<Route path="users" component={DashboardPage} />
</Route>
</Router>
</Provider>,
document.getElementById("application")
);
Note that the example in auth-flow utilizes localStorage to persist userAccess tokens, etc. I really don't want to use localStorage or cookies at this time. I want to minimize the complexity and store the login state in the store(state.user). I don't specifically want to go into the reason for this other than to say that there is a large amount of information that comes the authentication step that needs to be persisted to the store.
You can try doing something like
<Route
path="/"
component={Application}
onEnter={ (nextState, replace) => { isAuthorized(nextState, replace, store) } } >
And in your isAuthorized function
function isAuthorized( nextState, replace, store ) {
// Now you have access to the store, you can
const state = store.getState();
// your logic to validate whether the user is logged in or not
if ( state.user && state.user.loggedIn === true ) {
// this user is authenticated
// continue..
}
else {
// unauthenticated user, redirect the user to login page
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
});
}
}
(Posted solution on behalf of the OP).
This solution worked for me which is a slight modification on the answer:
function isAuthorized(nextState, replace) {
if (nextState.location.pathname === "/login") {
return;
}
const state = store.getState();
if (state.user && state.user.loggedIn === true) {
return;
}
replace("/login");
}
render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={Application} onEnter={(nextState, replace) => isAuthorized(nextState, replace)}>
<IndexRoute component={DashboardPage} />
<Route path="login" component={LoginPage} />
<Route path="dashboard" component={DashboardPage} />
<Route path="items" component={DashboardPage} />
<Route path="item-categories" component={DashboardPage} />
<Route path="modifiers" component={DashboardPage} />
<Route path="taxes" component={DashboardPage} />
<Route path="discounts" component={DashboardPage} />
<Route path="orders" component={DashboardPage} />
<Route path="users" component={DashboardPage} />
</Route>
</Router>
</Provider >,
document.getElementById("application")
);

Categories

Resources