Skip to content

Instantly share code, notes, and snippets.

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

react-router 4 basic setup

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 a user 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

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

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