React-router: Without onEnter, Is a right way my auth-flow with redux-sagas?

Created on 21 Apr 2017  路  3Comments  路  Source: ReactTraining/react-router

My app fetch authentication api every time, don't use localStorage.
fetch in auth saga.

store:

import { createStore, compose, applyMiddleware } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import rootReducer from '../redux';
//react-router 4.0
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';

export default function configureStore(initialState) {
    const history = createHistory();
    const routerReduxMiddleware = routerMiddleware(history);
    const sagaMiddleware = createSagaMiddleware();
    const middlewares = [
        routerReduxMiddleware,
        sagaMiddleware
    ];

    const store = createStore(rootReducer, initialState, compose(
        applyMiddleware(...middlewares)
    )
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}

reducer (redux/auth)

// auth reducer(ducks)
export const CHECK_USER_LOGIN = 'CHECK_USER_LOGIN';
export const CHECK_USER_LOGIN_SUCCESS = 'CHECK_USER_LOGIN_SUCCESS';
export const CHECK_USER_LOGIN_FAILURE = 'CHECK_USER_LOGIN_FAILURE';

export function actionCheckUserLogin() {
    return {
        type: CHECK_USER_LOGIN
    };
}
export function actionCheckUserLoginSuccess() {
    return {
        type: CHECK_USER_LOGIN_SUCCESS
    };
}
export function actionCheckUserLoginFail(error) {
    return {
        type: CHECK_USER_LOGIN_FAILURE,
        error
    };
}

let initialState = {
    error: '',
    loggedIn: false
};

export default function reducer(state = initialState, action) {
    switch (action.type) {
        case CHECK_USER_LOGIN_SUCCESS:
            return {
                "error": '',
                "loggedIn": true
            };
        case CHECK_USER_LOGIN_FAILURE:
            return {
                "loggedIn": false,
                "error": action.error
            };
        default:
            return state;
    }
}

// reducer index
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';

import { auth } from './redux/auth';

const rootReducer = combineReducers({
    router:routerReducer,
    auth
});

export default rootReducer;

saga (Remove some complicated logic)

import { take, call, put, fork, takeLatest } from 'redux-saga/effects';
import { CHECK_USER_LOGIN, actionCheckUserLoginSuccess, actionCheckUserLoginFail } from './redux/auth';
import request from './utils/request';

const API = {
    checkUserIsLogin: '/api/user/checkUserIsLogin',
};

function* fetchCheckUserIsLogin() {
    try {
        const response = yield call(request.get, API.checkUserIsLogin);
        if (response.success) {
            yield put(actionCheckUserLoginSuccess());
        } else {
            yield put(actionCheckUserLoginFail(response.message));
        }
    } catch (err) {
        yield put(actionCheckUserLoginFail(err));
    }
}

function* rootSaga() {
    yield[
        takeLatest(CHECK_USER_LOGIN, fetchCheckUserIsLogin),
        //fork(watchOrders),
        //fork(watchLogin)
    ];
}

export default rootSaga;

Entry:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import { ConnectedRouter } from 'react-router-redux';
import rootSaga from './sagas';
import configureStore from './store/configureStore';

import { NotFound } from './components';
import { App, Login, Register, UserHome, Messages, Orders, Customers } from './containers';
import { actionCheckUserLogin } from './redux/auth';

const store = configureStore();
store.runSaga(rootSaga);
// dispatch here?
store.dispatch(actionCheckUserLogin());

const history = createHistory();

render(
    <Provider store={store}>
        <ConnectedRouter history={history}>
            <Switch>
                <Route path="/" exact component={App} />
                <Route path="/login" component={Login} />
                <Route path="/register" component={Register} />
                <Route component={NotFound} />
                {/* authorized access */}
                <Route path="/user/home" component={UserHome} />
                <Route path="/user/messages" component={Messages} />
                <Route path="/user/orders" component={Orders} />
                <Route path="/user/customers" component={Customers} />
                {/* more... */}
            </Switch>
        </ConnectedRouter>
    </Provider>,
    document.getElementById('app')
);

utils:

function checkLogin(props) {
    if (!props.auth.loggedIn) {
        props.history.replace({
            pathname: '/login',
            state: {
                from: props.location
            }
        });
    }
}

export default {
    checkLogin
};

containers:

#UserHome
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import Utils from './utils';

const propTypes = {
    location: PropTypes.object,
    history: PropTypes.object,
    auth: PropTypes.object
};

function mapStateToProps(state) {
    return {
        auth: state.auth
    };
}

class UserHome extends Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        Utils.checkLogin(this.props);
    }

    render() {
        return (
            <div>
                UserHome
            </div>
            );
    }
}

UserHome.propTypes = propTypes;
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(UserHome));

#Messages
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import Utils from './utils';

const propTypes = {
    location: PropTypes.object,
    history: PropTypes.object,
    auth: PropTypes.object
};

function mapStateToProps(state) {
    return {
        auth: state.auth
    };
}

class UserHome extends Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        Utils.checkLogin(this.props);
    }

    render() {
        return (
            <div>
                Messages
            </div>
            );
    }
}

Messages.propTypes = propTypes;
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Messages));

Most helpful comment

No, the docs aren't going to cover every possible use case. They should describe how to use the APIs of this library and the rest is up to you.

All 3 comments

This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux. Thanks!

@timdorr Doesn't this highlight a "bug" with the docs if people can't figure out how to properly combine Router & Redux for authentication flow?

No, the docs aren't going to cover every possible use case. They should describe how to use the APIs of this library and the rest is up to you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andrewpillar picture andrewpillar  路  3Comments

yormi picture yormi  路  3Comments

davetgreen picture davetgreen  路  3Comments

Waquo picture Waquo  路  3Comments

wzup picture wzup  路  3Comments