问题
Backgrond:
I am creating a Login component.
saga.js is composed by 3 functions
1. rootSaga. It will execute the list of sagas inside
2. watchSubmitBtn. It will watch the click on the submit button and dispatch an action.
3. shootApiTokenAuth will receive dispatched action and process axios.post the return value is promise object
In action:
Backend returns 400 to the React. This case no problem I can read the payload and display in the render() easily. But when 200 is returned.
I need to let user go to the url /companies.
Attempt:
I had tried put this.props.history.push('/companies'); in the componentWillUpdate(), but it does not work. I have to click Submit 2 times to get React understand that token has been saved.
Login.js
import React, {Component} from 'react';
import ErrorMessage from "../ErrorMessage";
import {Field, reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import {validate} from '../validate';
import {SUBMIT_USERNAME_PASSWORD} from "../../constants";
class Login extends Component {
constructor(props) {
//Login is stateful component, but finally action will change
//reducer state
super(props);
const token = localStorage.getItem('token');
const isAuthenticated = !((token === undefined) | (token === null));
this.state = {
token,
isAuthenticated,
message: null,
statusCode: null
};
}
onSubmit(values) {
const {userid, password} = values;
const data = {
username: userid,
password
};
this.props.onSubmitClick(data);
}
componentWillUpdate(){
console.log('componentWillUpdate');
if(this.props.isAuthenticated){
this.props.history.push('/companies');
}
}
renderField(field) {
const {meta: {touched, error}} = field;
const className = `'form-group' ${touched && error ? 'has-danger' : ''}`;
console.log('renderField');
return (
<div className={className}>
<label>{field.label}</label>
<input
className="form-control"
type={field.type}
placeholder={field.placeholder}
{...field.input}
/>
<div className="text-help">
{touched ? error : ''}
</div>
</div>
);
}
render() {
const {handleSubmit} = this.props;
return (
<div>
<ErrorMessage
isAuthenticated={this.props.isAuthenticated}
message={this.props.message}
/>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
name="userid"
component={this.renderField}
placeholder="User ID"
type="text"
/>
<Field
name="password"
component={this.renderField}
placeholder="Password"
type="password"
/>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
<a className='btn btn-primary' href="https://www.magicboxasia.com/">Sign up</a>
</div>
);
}
}
const onSubmitClick = ({username, password}) => {
return {
type: SUBMIT_USERNAME_PASSWORD,
payload: {username, password}
};
};
const mapStateToProps = (state, ownProps) => {
return {
...state.login
}
};
export default reduxForm({
validate,
form: 'LoginForm'
})(
connect(mapStateToProps, {onSubmitClick})(Login)
);
saga.ja
const shootApiTokenAuth = (values) =>{
const {username, password} = values;
return axios.post(`${ROOT_URL}/api-token-auth/`,
{username, password});
};
function* shootAPI(action){
try{
const res = yield call(shootApiTokenAuth, action.payload);
yield put({
type: REQUEST_SUCCESS,
payload: res
});
}catch(err){
yield put({
type: REQUEST_FAILED,
payload: err
});
}
}
function * watchSubmitBtn(){
yield takeEvery(SUBMIT_USERNAME_PASSWORD, shootAPI);
}
// single entry point to start all Sagas at once
export default function* rootSaga() {
yield all([
watchSubmitBtn()
])
}
Problem:
How can I set the component state and push to url /companies? after backend returns 200?
回答1:
I usually handle conditional navigation like that in the saga.
The simplest answer with the existing code is to pass a history object as a prop in the SUBMIT_USERNAME_PASSWORD action and do the history.push() call in the success case of the saga, something like:
const onSubmitClick = ({username, password}) => {
const { history } = this.props;
return {
type: SUBMIT_USERNAME_PASSWORD,
payload: {username, password, history}
};
};
.......
function* shootAPI(action){
try{
const res = yield call(shootApiTokenAuth, action.payload);
const { history } = action.payload;
yield put({
type: REQUEST_SUCCESS,
payload: res
});
history.push('/companies');
}catch(err){
yield put({
type: REQUEST_FAILED,
payload: err
});
}
}
回答2:
I am not as experienced in react but you could achieve it as follows:
1.Create a new Module: history-wrapper.ts
export class HistoryWrapper {
static history;
static init(history){
HistoryWrapper.history = history;
}
}
2.In you login.jsx
HistoryWrapper.init(history);//initialize history in HistoryWrapper
3.Subsequently anywhere in your app
HistoryWrapper.history.push('/whatever');
回答3:
React has additional lifecycle. I just know it today. They are many of them.
componentDidUpdate() {
this.props.history.push('/companies');
}
回答4:
yield put(push('/path-to-go'));
solved my problem
来源:https://stackoverflow.com/questions/47864755/redux-saga-and-history-push