Search code examples
reactjsreduxreact-reduxredux-saga

React-Redux-Saga action payload undefined in reducer


I'm trying to create a basic counter application. However inside my reducer I don't seem to be able to call action.payload.step (The step would becoming from my action and it should represent the value passed in at dispatch.

Just to confirm, I want to add the step within my reducer because I shouldn't be calling + 1 I should be passing through the step value.

Reducer:

import {
  INCREMENT_REQUEST,
  INCREMENT_SUCCESS,
  INCREMENT_FAILURE,
  DECREMENT_REQUEST,
  DECREMENT_SUCCESS,
  DECREMENT_FAILURE,
} from "../actions/actionTypes";

const initialState = {
  value: 0,
  loading: null,
  error: null,
};

const counterReducers = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT_REQUEST:
      return { ...state, loading: true, error: null };
    case INCREMENT_SUCCESS:
      return {
        ...state,
        value: state.value + 1,
        loading: false,
        error: null,
      };
    case INCREMENT_FAILURE:
      return { ...state, error: action.error };
    case DECREMENT_REQUEST:
      return { ...state, loading: true, error: null };
    case DECREMENT_SUCCESS:
      return {
        ...state,
        value: state.value - 1,
        loading: false,
        error: null,
      };
    case DECREMENT_FAILURE:
      return { ...state, error: action.error };

    default:
      return state;
  }
};

export default counterReducers;

Actions:

import { INCREMENT_REQUEST, DECREMENT_REQUEST } from "./actionTypes";

export const incrementAction = (step) => {
  return {
    type: INCREMENT_REQUEST,
    payload: {
      step: step,
    },
  };
};

export const decrementAction = (step) => {
  return {
    type: DECREMENT_REQUEST,
    payload: {
      step: step,
    },
  };
};

Container:

import { useSelector, useDispatch } from "react-redux";
import CounterComponent from "../components/CounterComponent";

const CounterContainer = () => {
  const counter = useSelector((state) => state.counterReducers);

  const dispatch = useDispatch();

  return <CounterComponent counter={counter} dispatch={dispatch} />;
};

export default CounterContainer;

UI Component/Child component:

import React from "react";
import { decrementAction, incrementAction } from "../actions";

const CounterComponent = ({ counter, dispatch }) => {
  return (
    <div>
      <h1>Counter - Redux Saga Flow Example</h1>
      <button onClick={() => dispatch(incrementAction(1))}>
        Increment + 1
      </button>
      <button onClick={() => dispatch(decrementAction(1))}>
        Decrement - 1
      </button>
      {counter.loading ? <div>loading</div> : <div>Count: {counter.value}</div>}
    </div>
  );
};

export default CounterComponent;

Sagas:

import { takeEvery, call, put } from "redux-saga/effects";

const delay = (ms) => new Promise((res) => setTimeout(res, ms));

function* incrementAsync() {
  yield call(delay, 2000);
  yield put({ type: "INCREMENT_SUCCESS" });
}

export function* watchIncrement() {
  yield takeEvery("INCREMENT_REQUEST", incrementAsync);
}

function* decrementAsync() {
  yield call(delay, 2000);
  yield put({ type: "DECREMENT_SUCCESS" });
}

export function* watchDecrement() {
  yield takeEvery("DECREMENT_REQUEST", decrementAsync);
}

Any assistance would be appreciated!


Solution

  • Update 15/Sep 2021

    hey snazzyy, after your comment I reviewed your code and the missing piece is passing the payload from "_REQUEST", to the "_SUCCESS" action,

    when you do yield put({ type: "INCREMENT_SUCCESS" }), there's no "payload" object here, is just action.type inside your reducer,

    the functions used by takeEvery will recieve the original action object dispatched by redux, you just need to forward it to the final action,

    like the following:

    function* incrementAsync(action) {
      yield call(delay, 2000);
      yield put({ type: "INCREMENT_SUCCESS", payload: action.payload });
    }
    
    export function* watchIncrement() {
      yield takeEvery("INCREMENT_REQUEST", incrementAsync);
    }
    
    function* decrementAsync(action) {
      yield call(delay, 2000);
      yield put({ type: "DECREMENT_SUCCESS", payload: action.payload });
    }
    
    export function* watchDecrement() {
      yield takeEvery("DECREMENT_REQUEST", decrementAsync);
    }
    

    I also updated the codesandbox example: https://codesandbox.io/s/redux-saga-counter-async-wsep6

    ========== Original Answer

    have you initialize your sagas as middleware?

    for your sagas, something like:

    export default function* rootSaga() {
      yield all([
        watchIncrement(),
        watchDecrement()
      ])
    }
    

    and your store, like this:

    const sagaMiddleware = createSagaMiddleware()
    const store = ...
    sagaMiddleware.run(rootSaga)
    

    if you don't start them, they will not listen for the events in the store and trigger their actions

    i created a CodeSandbox with your example and adding the "rootSaga", looks like is working correctly:

    https://codesandbox.io/s/redux-saga-counter-async-wsep6

    a walkthrough about it can be found on: https://redux-saga.js.org/docs/introduction/BeginnerTutorial