Skip to content

Instantly share code, notes, and snippets.

@loopmode
Created May 16, 2018 09:07
Show Gist options
  • Save loopmode/af87a10da874aef70e65bd4c6cc28092 to your computer and use it in GitHub Desktop.
Save loopmode/af87a10da874aef70e65bd4c6cc28092 to your computer and use it in GitHub Desktop.
react-router-4-setup.md

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)

index.js

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

App.js

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

ProtectedRoute.js

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" />;
                    }
                }}
            />
        );
    }
}

LoginPage.js

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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment