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
?
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.
push(path, [state]): Pushes a new entry onto the history stack. Useful to redirect users to page. replace(path, [state]): Replaces the current entry on the history stack.
By working with effects, Redux Saga makes sagas declarative, rather than imperative, which adds the benefit of a function that returns a simple object, which is easier to test than a function that directly makes an asynchronous call.
takeLatest(pattern, saga, ... And automatically cancels any previous saga task started previously if it's still running. Each time an action is dispatched to the store. And if this action matches pattern , takeLatest starts a new saga task in the background.
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
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With