redux saga and history.push

风格不统一 提交于 2019-12-21 17:05:09

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!