This piece was originally posted on my blog at legobox
Background
I build frontend applications and APIs often, usually I use a variety of tools, but react has been something I find interesting, enabling me to build fast and quick, although I could argue that vue matches it word for word, but that’s not the argument of the day, enough of the react vs Vue bullshit, so I do both. They are both amazing and I’m glad there are alternatives of frameworks to choose from. In the process of developing and creating applications using react, I came up with a general structure and routing pattern for my applications.
I consider this something I’d love to share and hopefully improve with directives from the community.
Pre-requisites
To this article, there are only two pre-requisites, (last time I wrote a pre-requisite it spread like wildfire, hope it wouldn’t be asking too much for it to happen again 🙃 )
- You know about react, have some experience using it.
- You’ve heard of redux
- You are familiar with the react router.
If you got this down?, then you are good to roll.
The Problem
The problem entails navigating between pages using the react-router library, while also keeping track of authentication status, There's already a common understanding of this problem with respect to authentication and navigation, and usually it's known that you can solve this with a simple setup of react router and redux, But there are many patterns to this very setup, in this article, I’m going to explain mine.
The Solution
In order to navigate properly in the app, I set up with react-router and implement a 3 component auth check flow, one component checks for the visitors only pages, another component checks for the protected routes, and the third component encompasses all and does a general check for the application’s auth status, I know it sounds ambiguous now, but we are going to review the code struct and get into the nitty-gritty of things.
// import scenes here and make routes of them
import React from 'react';
import {Route} from 'react-router-dom';
// import scenes here
import {Dashboard, Settings, Login} from './Scenes'
// auth checkers for checking if the routes are authorized
import AppCheck from './Modules/AppCheck.jsx';
import EnsureLoggedInContainer from './Modules/EnsureLoggedInContainer.jsx';
import EnsureVisitorOnlyContainer from './Modules/EnsureVisitorOnlyContainer.jsx';
const routes = (
<React.Fragment>
<AppCheck>
<EnsureVisitorOnlyContainer>
<Route path="/login" component={Login} />
</EnsureVisitorOnlyContainer>
<EnsureLoggedInContainer>
<Route exact path="/home" component={Dashboard} />
<Route path="/settings" component={Settings} />
</EnsureLoggedInContainer>
</AppCheck>
</React.Fragment>
);
export default routes;
This was drawn directly from the codebase, as you can see, the idea is clearly presented.
The three components in this case are AppCheck
, EnsureVisitorOnlyContainer
, EnsureLoggedInContainer
.
Let’s have a look at the inner structure of these components.
AppCheck Component.
import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router";
class AppCheck extends React.Component {
componentDidUpdate(prevProps) {
const { dispatch, redirectUrl } = this.props;
const isLoggingOut = prevProps.isLoggedIn && !this.props.isLoggedIn;
const isLoggingIn = !prevProps.isLoggedIn && this.props.isLoggedIn;
if (isLoggingIn) {
// dispatch(navigateTo(redirectUrl))
} else if (isLoggingOut) {
// do any kind of cleanup or post-logout redirection here
}
}
render() {
return this.props.children;
}
}
const mapStateToProps = state => {
console.log(state);
return {
isLoggedIn: state.isAuthenticated,
redirectUrl: state.redirectUrl
};
};
export default withRouter(connect(mapStateToProps)(AppCheck));
As we can see, this component checks the prop on update and confirms the state of the previous isLoggedIn
status, to note if a user is logging in or logging out, depending on this, it redirects to another url specified in the redux setup, or it proceeds to logout if it's trying to log out, else it continues with the rendering of the props children.
EnsureVisitorsOnlyContainer
import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router";
// import actions from "../Store/Actions";
class EnsureVisitorOnlyContainer extends React.Component {
componentDidMount() {
const { currentURL } = this.props;
var visitorRoutes = ["/", "", "terms", "conditions"];
var check = visitorRoutes.indexOf(currentURL) > -1;
if (this.props.isLoggedIn) {
// set the current url/path for future redirection (we use a Redux action)
// then redirect (we use a React Router method)
// dispatch(actions.setRedirectUrl(currentURL))
if (check) {
this.props.history.replace("/home");
}
}
}
render() {
if (!this.props.isLoggedIn) {
return this.props.children;
} else {
return null;
}
}
}
// Grab a reference to the current URL. If this is a web app and you are
// using React Router, you can use `ownProps` to find the URL. Other
// platforms (Native) or routing libraries have similar ways to find
// the current position in the app.
function mapStateToProps(state, ownProps) {
console.log(ownProps);
return {
isLoggedIn: state.isAuthenticated,
currentURL: ownProps.location.pathname
};
}
export default withRouter(connect(mapStateToProps)(EnsureVisitorOnlyContainer));
This component set simply checks if the current route is amongst the visitors routes, if it is, then it checks the logged in status, if the app is logged in, it redirects the user to the /home
route.
Any route listed in this component set would be redirected to the Home route if their path is found in the visitors list.
EnsureLoggedInContainer
import React from 'react'
import {connect} from 'react-redux'
import { withRouter } from 'react-router'
import actions from '../Store/Actions'
class EnsureLoggedInContainer extends React.Component {
componentDidMount() {
const { dispatch, currentURL } = this.props
if (!this.props.isLoggedIn) {
// set the current url/path for future redirection (we use a Redux action)
// then redirect (we use a React Router method)
dispatch(actions.setRedirectUrl(currentURL))
this.props.history.replace("/")
}
}
render() {
if (this.props.isLoggedIn) {
return this.props.children
} else {
return null
}
}
}
// Grab a reference to the current URL. If this is a web app and you are
// using React Router, you can use `ownProps` to find the URL. Other
// platforms (Native) or routing libraries have similar ways to find
// the current position in the app.
function mapStateToProps(state, ownProps) {
console.log(ownProps)
return {
isLoggedIn: state.isAuthenticated,
currentURL: ownProps.location.pathname
}
}
export default withRouter(connect(mapStateToProps)(EnsureLoggedInContainer))
This container checks if the current state of the app is not loggedin, it redirects to the /
path, therefore all pages get to be managed via this structure.
AppCheck handling the process of logging out when the prevProp changes it's status, while EnsureVisitorsOnlyContainer and EnsureLoggedInContainer handle redirects on different instances depending on the route and within which container it's listed.
Conclusion
I am open to suggestions as to how this can be improved and to hear from you how you achieve yours, over time I hope to share other tricks and techniques I use in development with react and vue and help others learn how you can take huge advantage of the frameworks in building quicker than quick.
Top comments (2)
Hi @legobox_co in this blog i can't find where you put the const routes. i request you please share all code related to this topic. I am facing this error (You should not use or withRouter() outside a ). please tell me where i am doing wrong.
Link to the github repo would be nice