Basic react-router 4 setup for login and protected routes. Requires the user to log in in order to see those protected routes.
- Assuming there is a
UserStore
that performs actual operations (e.g. ajax calls) and provides auser
prop to components when the user is logged in. - Omitting any insiginificant import statements. This is not 100% copy-pastable, but still pretty much real-world code
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 path="/login" component={LoginPage} />
<ProtectedRoute exact path="/" component={DashboardPage} />
<ProtectedRoute 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 @withRouter
as a decorator (or alternatively export default withRouter(LoginPage)
).
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';
@withRouter
@connect([
{
store: UserStore,
props: ['user']
}
])
export default 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 });
}
}