我正在使用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