Reputation: 3
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 constructor
and 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
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 latest
NEWS_FETCH_LOADINGactions that was dispatched and will call the
getNews`.
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