成功登录后,React Router不会重定向

时间:2017-08-10 03:04:23

标签: reactjs react-redux redux-form react-router-v4

我正在使用React Router 4,Redux,Redux Sagas,Redux表格。我创建了一个私有路线,其中有一个道具。登录成功但未重定向。任何帮助表示赞赏。

login.js

    import React, {Component} from 'react';
    import {connect} from 'react-redux';
    import {reduxForm, Field} from 'redux-form';
    import PropTypes from 'prop-types';


    import { TextField } from 'redux-form-material-ui';
    import Checkbox from 'material-ui/Checkbox';
    import RaisedButton from 'material-ui/RaisedButton';

    import loginRequest from '../../actions/loginActions';

    //field validations
    import required from '../form-validation/required';
    import email from '../form-validation/email'

    class Login extends Component {

      // Pass the correct proptypes in for validation
      static propTypes = {
        handleSubmit: PropTypes.func,
        loginRequest: PropTypes.func,
        pristine: PropTypes.bool,
        login: PropTypes.shape({
          requesting: PropTypes.bool,
          successful: PropTypes.bool,
          messages: PropTypes.array,
          errors: PropTypes.array,
        }),
      }

    componentDidMount() {
        this.refs.name // the Field
          .getRenderedComponent() // on Field, returns ReduxFormMaterialUITextField
          .getRenderedComponent() // on ReduxFormMaterialUITextField, returns TextField
          .focus(); // on TextField
      }

      // Remember, Redux Form passes the form values to our handler
      // In this case it will be an object with `email` and `password`
      submit = (values) => {    
        this.props.loginRequest(values)
      }

    render (){  

      const {
          handleSubmit, 
          pristine,
          login: {
            requesting,
            successful,
            messages,
            errors,      
         }
        } = this.props

      return (
      <div>
        <form className="loginForm" onSubmit={handleSubmit(this.submit)}>
          <div className="login-wrapper">
            <div className="login-fields">
              <h3>Login <a href="">Forgot Password?</a></h3>

              <Field
                name="email"            
                component={TextField}            
                floatingLabelText="Username or Email"
                validate={[required, email]}
                fullWidth={true}
                ref="name"
                withRef
              />

              <Field
                name="password"            
                component={TextField}
                type="password"            
                floatingLabelText="Password"
                validate={required}
                fullWidth={true}
                ref="name"
                withRef
              />

              <div className="pt20">
                <Checkbox
                  label="Remember Me"
                />
              </div>
              <div className="pt20">
                <RaisedButton type="submit" label="Log In" primary={true} fullWidth={true} disabled={pristine || requesting}/>
              </div>
            </div>
          </div>
        </form>
      </div>
    );
    }

    }

    // Grab only the piece of state we need
    const mapStateToProps = state => ({
      login: state.login,
    })

    // make Redux state piece of `login` and our action `loginRequest`
    // available in this.props within our component
    const connected = connect(mapStateToProps, { loginRequest })(Login)

    const LoginForm = reduxForm({
      form: 'loginForm',
    })(connected)

    export default LoginForm;

loginReducer.js

    import {
      LOGIN_REQUESTING,
      LOGIN_SUCCESS,
      LOGIN_ERROR,
    } from '../constants/loginConstants'

    const initialState = {
      requesting: false,
      successful: false,
      messages: [],
      errors: [],
    }

    const reducer = function loginReducer (state = initialState, action) {
      switch (action.type) {
        // Set the requesting flag and append a message to be shown
        case LOGIN_REQUESTING:
          return {
            requesting: true,
            successful: false,
            messages: [{ body: 'Logging in...', time: new Date() }],
            errors: [],
          }

        // Successful?  Reset the login state.
        case LOGIN_SUCCESS:
          return {
            errors: [],
            messages: [],
            requesting: false,
            successful: true,
          }

        // Append the error returned from our api
        // set the success and requesting flags to false
        case LOGIN_ERROR:
          return {
            errors: state.errors.concat([{
              body: action.error.toString(),
              time: new Date(),
            }]),
            messages: [],
            requesting: false,
            successful: false,
          }

        default:
          return state
      }
    }

    export default reducer

loginSagas.js

    import { take, fork, cancel, call, put, cancelled } from 'redux-saga/effects'

    // Helper for api errors
    import { handleApiErrors } from '../lib/api-errors'

    import history from '../history'

    // Our login constants
    import {
      LOGIN_REQUESTING,
      LOGIN_SUCCESS,
      LOGIN_ERROR,
    } from '../constants/loginConstants'

    // So that we can modify our Client piece of state
    import {
      setClient,
      unsetClient,
    } from '../client/actions'

    import {
      CLIENT_UNSET,
    } from '../client/constants'

    const loginUrl = `${process.env.REACT_APP_API_URL}${process.env.REACT_APP_API_AUTHENTICATE}`

    function loginApi (email, password) {  
      return fetch(loginUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ email, password }),
      })
        .then(handleApiErrors)
        .then(response => response.json())
        .then(json => json)
        .catch((error) => { throw error })
    }

    function* logout () {
      // dispatches the CLIENT_UNSET action
      yield put(unsetClient())

      // remove our token
      localStorage.removeItem('token')

      // redirect to the /login screen
      // browserHistory.push('/login')
      yield call(history.push, '/login');
    }

    function* loginFlow (email, password) {  
      let token
      try {
        // try to call to our loginApi() function.  Redux Saga
        // will pause here until we either are successful or
        // receive an error
        let response = yield call(loginApi, email, password)
        token = response.data.token    

        // inform Redux to set our client token, this is non blocking so...
        yield put(setClient(token))

        // .. also inform redux that our login was successful
        yield put({ type: LOGIN_SUCCESS })

        // set a stringified version of our token to localstorage on our domain
        localStorage.setItem('token', JSON.stringify(token))

        // redirect them to WIDGETS!
        // browserHistory.push('/widgets')

        yield call(history.push, '/mail-confirmation');

      } catch (error) {
        // error? send it to redux
        yield put({ type: LOGIN_ERROR, error })
      } finally {
        // No matter what, if our `forked` `task` was cancelled
        // we will then just redirect them to login
        if (yield cancelled()) {
          // browserHistory.push('/login')
          yield call(history.push, '/login');
        }
      }

      // return the token for health and wealth
      return token
    }

    // Our watcher (saga).  It will watch for many things.
    function* loginWatcher () {
      // Generators halt execution until their next step is ready/occurring
      // So it's not like this loop is firing in the background 1000/sec
      // Instead, it says, "okay, true === true", and hits the first step...
      while (true) {
        //
        // ... and in this first it sees a yield statement with `take` which
        // pauses the loop.  It will sit here and WAIT for this action.
        //
        // yield take(ACTION) just says, when our generator sees the ACTION
        // it will pull from that ACTION's payload that we send up, its
        // email and password.  ONLY when this happens will the loop move
        // forward...
        const { email, password } = yield take(LOGIN_REQUESTING)

        // ... and pass the email and password to our loginFlow() function.
        // The fork() method spins up another "process" that will deal with
        // handling the loginFlow's execution in the background!
        // Think, "fork another process".
        //
        // It also passes back to us, a reference to this forked task
        // which is stored in our const task here.  We can use this to manage
        // the task.
        //
        // However, fork() does not block our loop.  It's in the background
        // therefore as soon as our loop executes this it mores forward...
        const task = yield fork(loginFlow, email, password)

        // ... and begins looking for either CLIENT_UNSET or LOGIN_ERROR!
        // That's right, it gets to here and stops and begins watching
        // for these tasks only.  Why would it watch for login any more?
        // During the life cycle of this generator, the user will login once
        // and all we need to watch for is either logging out, or a login
        // error.  The moment it does grab either of these though it will
        // once again move forward...
        const action = yield take([CLIENT_UNSET, LOGIN_ERROR])

        // ... if, for whatever reason, we decide to logout during this
        // cancel the current action.  i.e. the user is being logged
        // in, they get impatient and start hammering the logout button.
        // this would result in the above statement seeing the CLIENT_UNSET
        // action, and down here, knowing that we should cancel the
        // forked `task` that was trying to log them in.  It will do so
        // and move forward...
        if (action.type === CLIENT_UNSET) yield cancel(task)

        // ... finally we'll just log them out.  This will unset the client
        // access token ... -> follow this back up to the top of the while loop
        yield call(logout)
      }
    }

    export default loginWatcher

RoutesComponent.js

    import React, {Component} from 'react';
    import {Route, Switch, withRouter } from 'react-router-dom';
    import {connect} from 'react-redux';
    import PropTypes from 'prop-types';


    import Home from '../pages/home.js';

    import LoginPage from '../pages/login.js';
    import RegisterPage from '../pages/register.js';
    import ForgotPasswordPage from '../pages/forgotPassword.js';
    import MailConfirmPage from '../pages/mailConfirm.js';
    import Page404 from '../pages/page404.js';

    import PrivateRoute from './privateRoute.js'


    class RoutesComponent extends Component {



      render() {            
        return (
          <div>      
            <Switch>  
              <PrivateRoute exact authed="{this.props.authed}" path="/" component={Home}/>                          
              <PrivateRoute path="/mail-confirmation" authed="{this.props.authed}" component={MailConfirmPage}/>          
              <Route path="/login" component={LoginPage}/>
              <Route path="/register" component={RegisterPage}/>
              <Route path="/forgot-password" component={ForgotPasswordPage}/>          
              <Route path="/404" component={Page404}/>                
          </Switch>
          </div>
        );
      }
    }

    function mapStateToProps (state) {
      return {
        authed : state.login.successful
      }
    }

    export default withRouter(connect(mapStateToProps)(RoutesComponent));

PrivateRoute.js

    import React, {Component} from 'react';
    import { Redirect, Route } from 'react-router-dom';

    const PrivateRoute = function PrivateRoute ({component: Component, authed, ...rest}) {  
      return (
        <Route
          {...rest}
          render={(props) => authed === true
            ? <Component {...props} />
            : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
        />
      )
    }

    export default PrivateRoute

当在sagas中成功登录时,history.push会转到/ mail-confirmation组件。我看到的是浏览器中的地址栏更改为/ mail-confirmation,但页面仍然是登录页面

非常感谢任何帮助。

感谢 Sanjeev

0 个答案:

没有答案