AND
AND

Reputation: 3

reactjs redux Actions must be plain objects

I have a problem "Actions must be plain objects. Use custom middleware for async actions."

I'm using reactjs with this boilerplate (https://github.com/react-boilerplate/react-boilerplate/) I waste 1 day to fix this problem, but no result. I was trying move fetch request to action (without saga) and result the same.

My component:

...
import { compose } from 'redux';
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
import { successFetching } from './actions';
import reducer from './reducers';
import saga from './saga';

class NewsListPage extends React.PureComponent {

  componentDidMount() {
    const { dispatch } = this.props
    dispatch(saga())
  }

  ...
};

NewsListPage.propTypes = {
  isFetching: PropTypes.bool.isRequired,
  isSuccess: PropTypes.bool.isRequired,
  items: PropTypes.array.isRequired,
  dispatch: PropTypes.func.isRequired,
}

const selector = (state) => state;

const mapStateToProps = createSelector(
  selector,
  (isFetching, isSuccess, items) => ({ isFetching, isSuccess,items })
);

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
  };
}
const withReducer = injectReducer({ key: 'NewsList', reducer });
const withSaga = injectSaga({ key: 'NewsList', saga });
const withConnect = connect(mapStateToProps, mapDispatchToProps);

export default compose(
    withReducer,
    withSaga,
    withConnect
  )(NewsListPage);

My actions:

export const NEWS_FETCH_LOADING = 'NEWS_FETCH_LOADING';
export const NEWS_FETCH_FAILURE = 'NEWS_FETCH_FAILURE';
export const NEWS_FETCH_SUCCESS = 'NEWS_FETCH_SUCCESS';

export function preFetching() {
  return {
    type: NEWS_FETCH_LOADING,
  }
}

export function successFetching(json) {
  return {
    type: NEWS_FETCH_SUCCESS,
    payload: json
  }
}

export function failureFetching(error) {
  return {
    type: NEWS_FETCH_FAILURE,
    payload: error
  }
}

My reducers:

...
import { NEWS_FETCH_LOADING, NEWS_FETCH_FAILURE, NEWS_FETCH_SUCCESS } from './actions'

const INITIAL_STATE = {
  isFetching: false,
  isSuccess: false,
  items: []
};

function NewsListReducer(state = INITIAL_STATE, action) {
  switch (action.type) {
    case NEWS_FETCH_LOADING:
    case NEWS_FETCH_FAILURE:
      return Object.assign({}, state, {
        isFetching: true,
        isSuccess: false
      })
    case NEWS_FETCH_SUCCESS:
      return Object.assign({}, state, {
        isFetching: false,
        isSuccess: true,
        items: action.payload,
      })
    default:
      return Object.assign({}, state)
  }
}

export const rootReducer = combineReducers({
  NewsListReducer
})
export default rootReducer

My saga:

import { call, put, select, takeLatest } from 'redux-saga/effects';
import {NEWS_FETCH_LOADING, NEWS_FETCH_FAILURE, NEWS_FETCH_SUCCESS, preFetching, successFetching, failureFetching} from './actions';
import request from 'utils/request';

export function* getNews() {
  const requestURL ='%MY_URL%';

  try {
    const req = yield call(request, requestURL);
    yield put(successFetching(req));
  } catch (err) {
    yield put(failureFetching(err));
  }
}

export default function* NewsListSaga() {
  yield takeLatest(NEWS_FETCH_SUCCESS, getNews);
}

EDIT: @Alleo Indong, i tried your advice and its almost work.

I change in component mapDispatchToProps to

export function mapDispatchToProps(dispatch) {
  return {
    getData: () => dispatch(loadNews()),
  };
}

And add new function to actions.js

export function loadNews() {
  return {
    type: NEWS_FETCH_SUCCESS,
  }
}

But now ajax sent every seconds like in while cycle. I tried call this.props.getData(); in componentDidMount and in constructorand result same.

EDIT 2 In component i add

import * as  actionCreators from './actions';
import { bindActionCreators } from 'redux';

In constructor i change

constructor(props) {
  super(props);

  const {dispatch} = this.props;

  this.boundActionCreators = bindActionCreators(actionCreators, dispatch)
}

But here dispatch is undefined and in componentDidMount too. And change mapDispatchToProps to

export function mapDispatchToProps(dispatch) {
  return {
    ...bindActionCreators(actionCreators, dispatch),
  };
}

Upvotes: 0

Views: 457

Answers (1)

Alleo Indong
Alleo Indong

Reputation: 327

Hello @AND and welcome to stackoverflow! As I mentioned in the comment on the main post, you are dispatching a GENERATOR instead of an object on your

dispatch(saga());

Here's an example to help you

On your component import the actions that you want to use like this.

import * actionCreators from './actions';
import { bindActionCreators } from 'redux';

Learn more about bindActionCreators here enter link description here

This will import all of the exported actionCreators that you created there. In my opinion you don't need successFetching and failureFetching anymore as you can dispatch this actions later on on your saga

Then in your mapDispatchToProps you would want to register this actioncreator

function mapDispatchToProps(dispatch) {
    return {
        ...bindActionCreators(actionCreators, dispatch),
    };
}

Then on your saga, I want to point up some problems here as well. First your function

export default function* NewsListSaga() {
    yield takeLatest(NEWS_FETCH_SUCCESS, getNews);
}

Is actually what we call a watcher where it watch when a certain action was dispatch, in this function you are already waiting for the NEWS_FETCH_SUCCESS before getNews is called which is wrong, because you first have do the FETCHING before you will know if it is failed or success so yeah this function should be like this

export default function* NewsListSaga() {
    yield takeLatest(NEWS_FETCH_LOADING, getNews);
}

This simple means that you will take all the latestNEWS_FETCH_LOADINGactions that was dispatched and will call thegetNews`.

then on your getNews generator function, you can do it like this

export function* getNews() {
    const requestURL ='%MY_URL%';

    try {
        const req = yield call(request, requestURL);
        yield put({type: NEWS_FETCH_SUCCESS, req});
    } catch (err) {
        yield put({type: NEWS_FETCH_FAILED, err});
    }
}

in here

const req = yield call(request, requestURL);

You are saying that you will wait for the result of the request / service that you called, it might be a promise.

Then in here, this is why you won't need the functions successFetching and failureFetching functions anymore, since you can do it like this yield put({type: NEWS_FETCH_SUCCESS, req});

One last important step that you have to do now is to call the actionCreator inside your componentDidMount()

like this

componentDidMount() {
    const { preFetching } = this.props;
    preFetching();
}

Upvotes: 1

Related Questions