Basic react-router 4 setup for login and protected routes.
Assuming there is a UserStore that can perform the actual operations. It also provides a user prop to pages when the user is logged in.
Omitting any insiginificant import statements. This is not copy-pastable code, but still real-world code. (Omitted some project/ui-specific stuff)
Renders the App, but wrapped in a Router. That way we can use Route and Switch components anywhere in our app tree.
import { Router } from 'react-router-dom';
import App from './App';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
Defines the "outline" of our app - a mapping of routes and components.
Note how we use the regular Route and the custom ProtectedRoute.
import { Route, Switch } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
export default class App extends Component {
render() {
return (
<div className={cx('App', css.App)}>
<Switch>
<Route exact path="/login" component={LoginPage} />
<ProtectedRoute exact path="/" component={DashboardPage} />
<ProtectedRoute exact path="/dashboard" component={DashboardPage} />
<ProtectedRoute path="/concept/:conceptID?/:selectedID?" component={ConceptPage} />
<ProtectedRoute path="/settings" component={SettingsPage} />
</Switch>
</div>
);
}
}
A special route that will redirect to /login unless already logged in.
Note how it expects to receive a user prop to know whether it's logged in or not.
In this example, it uses a decorator to retrieve the value from a store. The user value will be either undefined or an object.
We use the render prop of Route and decide what to render based the user value.
import { Route, Redirect } from 'react-router-dom';
@connect([
{
store: UserStore,
props: ['user']
}
])
export default class ProtectedRoute extends PureComponent {
static propTypes = {
user: PropTypes.object,
component: PropTypes.oneOfType([PropTypes.node, PropTypes.element, PropTypes.func])
};
render() {
const { user, component: Component, ...props } = this.props;
return (
<Route
{...props}
render={props => {
if (user) {
return <Component {...props} />;
} else {
return <Redirect to="/login" />;
}
}}
/>
);
}
}
Displays a login form, keeps user input in its state and finally submits it.
Notice how it expects the location and retrieves it using export default withRouter().
It uses the UserStore.login() method, which will eventually cause the user prop to become populated.
That will cause a re-render which will redirect to the originally requested page..
import { Redirect, withRouter, Link } from 'react-router-dom';
@connect([
{
store: UserStore,
props: ['user']
}
])
class LoginPage extends Component {
static propTypes = {
user: PropTypes.object,
location: PropTypes.shape({
state: PropTypes.shape({
from: PropTypes.string
})
})
};
state = {
username: '',
password: ''
};
render() {
const { user } = this.props;
const { username, password } = this.state;
const { from } = this.props.location.state || { from: { pathname: '/' } };
if (user) {
return <Redirect to={from} />;
}
return (
<div className={cx('Page LoginPage', css.LoginPage)}>
<h3>Login</h3>
<form onSubmit={this.handleSubmit}>
<div>
<label>Username</label>
<input
type="text"
value={username}
onChange={e => this.setState({ username: e.target.value })}
/>
</div>
<div>
<label>Password</label>
<input
type="password"
value={password}
onChange={e => this.setState({ password: e.target.value })}
/>
</div>
<footer>
<Link to="/register">Register</Link>
<Button onClick={this.handleSubmit} type="submit" disabled={!username || !password}>
Login
</Button>
</footer>
</form>
</div>
);
}
@autobind
handleSubmit(e) {
e.preventDefault();
e.stopPropagation();
this.login();
}
@autobind
login() {
const { username, password } = this.state;
if (!username || !password) return;
UserStore.login({ username, password });
}
}
export default withRouter(LoginPage);