简体   繁体   English

Redux-Saga一次执行两次运行

[英]Redux-Saga is running twice for a single action

I have a saga to handle like requests. 我有一个传奇来处理类似的请求。 The user clicks a button to toggle the liked status of a photo. 用户单击按钮以切换照片的喜欢状态。

The saga listens for an action of type SUBMIT_LIKE . 传奇侦听SUBMIT_LIKE类型的SUBMIT_LIKE My problem is that the submitLikeSaga is running twice for each SUBMIT_LIKE action. 我的问题是对于每个SUBMIT_LIKE操作, submitLikeSaga运行两次。 eg In the api-error case one SUBMIT_LIKE action triggers two api calls and four RECEIVE_LIKE actions. 例如,在api错误的情况下,一个SUBMIT_LIKE操作将触发两个api调用和四个RECEIVE_LIKE操作。

(using react-boilerplate if that helps.) (如果有帮助,请使用反应样板 。)

export function* submitLikeSaga(action) {

  // optimistically update the UI
  // action shape: {
  //    type: RECEIVE_LIKE,
  //    like: {id: 1, liked: true}
  //  }
  yield put(receiveLike(action.like));

  // POST like data to api
  const response = yield call(
    request,
    `${API_ENDPOINT}/user_likes.json`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${action.token}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(action.like),
    }
  );

  // if api call failed, reverse change made to UI
  if (response.err) {
    yield put(receiveLike({
      id: action.like.id,
      liked: !action.like.liked,
    }));
  }
}

export function* watchSubmitLike() {
  yield* takeEvery(SUBMIT_LIKE, submitLikeSaga);
}

// All sagas to be loaded
export default [
  watchFetchUsers,
  watchSubmitLike,
];

EDIT: Add middleware and view code. 编辑:添加中间件并查看代码。

ProfileGrid.js ProfileGrid.js

const ProfileGrid = ({
  users,
  submitLike,
  token,
}) =>
  <div className={styles.profileGrid}>
    {users.map((user, i) => (
      <div key={i} className={styles.gridTile}>
        <GridTile
          title={user.username}
          actionIcon={<ActionIcon
            onIconClick={() => { submitLike(user.id, !user.liked, token); }}

            isActive={user.liked}
            activeColor="yellow"
            defaultColor="white"
          />}
        >
          <img style={{ width: '100%', height: 'auto' }} src={user.avatar} alt="profile" />
        </GridTile>
      </div>
    ))}
  </div>;

ActionIcon.js ActionIcon.js

const ActionIcon = ({

  onIconClick,
  isActive,
  activeColor,
  defaultColor,
}) =>
  <IconButton onClick={onIconClick} >
    <StarBorder
      color={isActive ? activeColor : defaultColor}
    />
  </IconButton>;

store.js store.js

/**
 * Create the store with asynchronously loaded reducers
 */

import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';

const sagaMiddleware = createSagaMiddleware();
const devtools = window.devToolsExtension || (() => (noop) => noop);

export default function configureStore(initialState = {}, history) {
  // Create the store with two middlewares
  // 1. sagaMiddleware: Makes redux-sagas work
  // 2. routerMiddleware: Syncs the location/URL path to the state
  const middlewares = [
    sagaMiddleware,
    routerMiddleware(history),
  ];

  const enhancers = [
    applyMiddleware(...middlewares),
    devtools(),
  ];

  const store = createStore(
    createReducer(),
    fromJS(initialState),
    compose(...enhancers)
  );

  // Extensions
  store.runSaga = sagaMiddleware.run;
  store.asyncReducers = {}; // Async reducer registry

  // Make reducers hot reloadable, see http://mxs.is/googmo
  /* istanbul ignore next */
  if (module.hot) {
    module.hot.accept('./reducers', () => {
      System.import('./reducers').then((reducerModule) => {
        const createReducers = reducerModule.default;
        const nextReducers = createReducers(store.asyncReducers);

        store.replaceReducer(nextReducers);
      });
    });
  }

  return store;
}

asyncInjectors.js asyncInjectors.js

import { conformsTo, isEmpty, isFunction, isObject, isString } from 'lodash';
import invariant from 'invariant';
import warning from 'warning';
import createReducer from '../reducers';

/**
 * Validate the shape of redux store
 */
export function checkStore(store) {
  const shape = {
    dispatch: isFunction,
    subscribe: isFunction,
    getState: isFunction,
    replaceReducer: isFunction,
    runSaga: isFunction,
    asyncReducers: isObject,
  };
  invariant(
    conformsTo(store, shape),
    '(app/utils...) asyncInjectors: Expected a valid redux store'
  );
}

/**
 * Inject an asynchronously loaded reducer
 */
export function injectAsyncReducer(store, isValid) {
  return function injectReducer(name, asyncReducer) {
    if (!isValid) checkStore(store);

    invariant(
      isString(name) && !isEmpty(name) && isFunction(asyncReducer),
      '(app/utils...) injectAsyncReducer: Expected `asyncReducer` to be a reducer function'
    );

    store.asyncReducers[name] = asyncReducer; // eslint-disable-line no-param-reassign
    store.replaceReducer(createReducer(store.asyncReducers));
  };
}

/**
 * Inject an asynchronously loaded saga
 */
export function injectAsyncSagas(store, isValid) {
  return function injectSagas(sagas) {
    if (!isValid) checkStore(store);

    invariant(
      Array.isArray(sagas),
      '(app/utils...) injectAsyncSagas: Expected `sagas` to be an array of generator functions'
    );

    warning(
      !isEmpty(sagas),
      '(app/utils...) injectAsyncSagas: Received an empty `sagas` array'
    );

    sagas.map(store.runSaga);
  };
}

/**
 * Helper for creating injectors
 */
export function getAsyncInjectors(store) {
  checkStore(store);

  return {
    injectReducer: injectAsyncReducer(store, true),
    injectSagas: injectAsyncSagas(store, true),
  };
}

Your app will work fine if you make following 如果您关注以下内容,则您的应用将正常运行

yield* takeEvery(SUBMIT_LIKE, submitLikeSaga); yield * takeEvery(SUBMIT_LIKE,submitLikeSaga); should be 应该

yield takeEvery(SUBMIT_LIKE, submitLikeSaga); 产生takeEvery(SUBMIT_LIKE,submitLikeSaga);

You should use yield* only when you are sequencing sagas 仅在对Sagas进行测序时才应使用yield *

According to documentation : https://redux-saga.js.org/docs/api/index.html#takelatestpattern-saga-args this will take your latest call and and will fire only one action 根据文档: https : //redux-saga.js.org/docs/api/index.html#takelatestpattern-saga-args这将接您的最新电话,并且只会触发一项操作

import { fork, takeLatest } from 'redux-saga/effects';

export function* watchSubmitLike() {
  yield fork(takeLatest, SUBMIT_LIKE, submitLikeSaga);

}

I had this happen recently. 我最近发生了这种情况。 In my case the saga was getting created twice. 就我而言,传奇故事被创建了两次。 We we withSaga HOC (generally in a container) 我们使用withSaga HOC(通常在容器中)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM