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);