Redux-Thunk - Async action creators Promise and chaining not working - javascript

I am trying to dispatch an action. I found working examples for some actions, but not as complex as mine.
Would you give me a hint? What am I doing wrong?
I am using TypeScript and have recently removed all typings and simplified my code as much as possible.
I am using redux-thunk and redux-promise, like this:
import { save } from 'redux-localstorage-simple';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';
const middlewares = [
save(),
thunkMiddleware,
promiseMiddleware,
];
const store = createStore(
rootReducer(appReducer),
initialState,
compose(
applyMiddleware(...middlewares),
window['__REDUX_DEVTOOLS_EXTENSION__'] ? window['__REDUX_DEVTOOLS_EXTENSION__']() : f => f,
),
);
Component - Foo Component:
import actionFoo from 'js/actions/actionFoo';
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Foo {
constructor(props) {
super(props);
this._handleSubmit = this._handleSubmit.bind(this);
}
_handleSubmit(e) {
e.preventDefault();
this.props.doActionFoo().then(() => {
// this.props.doActionFoo returns undefined
});
}
render() {
return <div onClick={this._handleSubmit}/>;
}
}
const mapStateToProps = ({}) => ({});
const mapDispatchToProps = {
doActionFoo: actionFoo,
};
export { Foo as PureComponent };
export default connect(mapStateToProps, mapDispatchToProps)(Foo);
Action - actionFoo:
export default () => authCall({
types: ['REQUEST', 'SUCCESS', 'FAILURE'],
endpoint: `/route/foo/bar`,
method: 'POST',
shouldFetch: state => true,
body: {},
});
Action - AuthCall:
// extremly simplified
export default (options) => (dispatch, getState) => dispatch(apiCall(options));
Action - ApiCall:
export default (options) => (dispatch, getState) => {
const { endpoint, shouldFetch, types } = options;
if (shouldFetch && !shouldFetch(getState())) return Promise.resolve();
let response;
let payload;
dispatch({
type: types[0],
});
return fetch(endpoint, options)
.then((res) => {
response = res;
return res.json();
})
.then((json) => {
payload = json;
if (response.ok) {
return dispatch({
response,
type: types[1],
});
}
return dispatch({
response,
type: types[2],
});
})
.catch(err => dispatch({
response,
type: types[2],
}));
};

From redux-thunk
Redux Thunk middleware allows you to write action creators that return
a function instead of an action
So it means that it doesn't handle your promises. You have to add redux-promise for promise supporting
The default export is a middleware function. If it receives a promise,
it will dispatch the resolved value of the promise. It will not
dispatch anything if the promise rejects.
The differences between redux-thunk vs redux-promise you can read here

Okay, after several hours, I found a solution. redux-thunk had to go first before any other middleware. Because middleware is called from right to left, redux-thunk return is last in chain and therefore returns the Promise.
import thunkMiddleware from 'redux-thunk';
const middlewares = [
thunkMiddleware,
// ANY OTHER MIDDLEWARE,
];
const store = createStore(
rootReducer(appReducer),
initialState,
compose(
applyMiddleware(...middlewares),
window['__REDUX_DEVTOOLS_EXTENSION__'] ? window['__REDUX_DEVTOOLS_EXTENSION__']() : f => f,
),
);

Related

Why is my redux-observable epic not being triggered?

trying to understand rxjs and rxjs within redux and redux observables by trying to do a simple fetch example
got my store set up like so:
import { applyMiddleware, createStore } from 'redux'
import { reducers } from 'redux/reducers'
import { createEpicMiddleware } from 'redux-observable'
import rootEpic from '../epics'
const epicMiddleware = createEpicMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
epicMiddleware.run(rootEpic)
export const store = createStore(reducers, composeEnhancers(applyMiddleware(epicMiddleware)))
and in my epic I've got
const getUserDataEpic = (action$, state$) =>
action$.pipe(
ofType('GET_USER_DATA'),
mergeMap(async (action) => {
const url = `my-url-is-here`
const data = await fetch(url).then((res) => res.json())
return Object.assign({}, action, { type: 'GET_DATA_SUCCESS', data })
}),
catchError((err) => Promise.resolve({ type: 'FETCH_ERROR', message: err.message })),
)
const epic2 = (action$, state$) => {}
export default combineEpics(getUserDataEpic)
I also have my action creator:
export const fetchData = () => ({ type: 'GET_USER_DATA' })
this gets fired in my component on mount. I've wrapped in mapDispatchToProps and I've verified it's definitely getting called. as is my reducer
I don't understand why my epic is not being triggered tho?? I was hoping it would see the GET_USER_DATA being fired and then fire it's own action to put the resolved API request into my state.
please advise where im going wrong
ok I figured it out
export const store = createStore(reducers, composeEnhancers(applyMiddleware(epicMiddleware)))
epicMiddleware.run(rootEpic)
I had to call these the other way around ^ :facepalm:

typesafe-actions(createStandardAction) not working on server with redux

I'm trying to dispatch an action which is by using createStandardAction(typesafe-actions) and then it goes to epic(redux-observable) for api call.
The strange part is that it works perfectly with stub data, it completes the flow(i.e., component->action->epic->reducer->store) but the action doesn't trigger or enters the epic while using it with the actual server
**Component:-**
export const mapDispatchToProps = (dispatch: Dispatch): ReduxActions => ({
loadTestData: () => dispatch(loadTestData())
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(withNavigation(loadData))
**Action**
import { ActionsUnion, createStandardAction } from 'typesafe-actions'
export const LOADDATA_GET = 'LOADDATA_GET'
export const loadData = createStandardAction(LOADDATA_GET)<void>()
const actions = {
loadData
}
export type AllActions = ActionsUnion<typeof actions>
**Epic**
import { Action, MiddlewareAPI } from 'redux'
import { ActionsObservable, Epic } from 'redux-observable'
import { Observable } from 'rxjs'
import {
LOADDATA_GET
} from './loadData.actions'
export const getloadDataEpic: Epic<Action, ReduxState> = (
action$: ActionsObservable<any>,
store: MiddlewareAPI<any, ReduxState>,
{ mobileAPI }: EpicDependencies
) =>
action$
.ofType(LOADDATA_GET)
.mergeMap((action) => {
return Observable.merge(
mobileAPI
.getJSON('/dummypath/loadData')
.mergeMap((response) => {
return Observable.of<any>(
setLoadData(response)
)
})
)}
)
.catch((error) => {
return Observable.of(errorAction(error))
})
I am really confused why the flow doesn't comes to epic for the actual server while for local json data and dummy path it works
Issue fixed, there was some data-mapping issue on the server side

redux-react error: Actions must be plain objects

i`m learning redux and after setting all the setup i get this message:
Error: Actions must be plain objects. Use custom middleware for async actions.
I readed 10 different issues with this mistake but not single solution works for me. here is my project on github, in case problem not in following code:https://github.com/CodeNinja1395/Test-task-for-inCode/tree/redux
store:
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
export default store;
actions:
import {FETCH_DATA} from './types';
export const fetchData = () => dispatch => {
fetch('https://raw.githubusercontent.com/CodeNinja1395/Test-task-for-inCode/master/clients.json')
.then(posts =>
dispatch({
type: FETCH_DATA,
payload: posts
})
);
};
You use an old version of redux - v1, while the new version is v4. When you upgrade to v4 it works.
npm install redux#^4.0.0
You're not returning your fetch() call. You have to make sure you return your fetch call inside of the action creator.
export function fetchData() {
return function(dispatch) {
return fetch( ... ).then( ... ).catch( ... )
}
}
export const fetchData = (params) => dispatch => {
dispatch(beginAjaxCall());
return fetch(...).then(response => {
dispatch(someFunction(response));
}).catch(error => {
throw(error);
});
}
export const loadIncidents = (params) => dispatch => {
dispatch(beginAjaxCall());
return IncidentsApi.getIncidents(params).then(incidents => {
dispatch(loadIncidentsSuccess(incidents));
}).catch(error => {
throw(error);
});
}
The top is ES5, middle ES6, and third is an ES6 version of a function that I used in a project.
Hope that helps.

Cannot read property 'then' of undefined

So I'm using react + redux, and I'm continuing to get the following error: "Cannot read property 'then' of undefined". For some reason the promise isn't being returned. I'm also particularly new to using redux thunk.
Reducer
import { merge } from 'lodash';
import * as APIutil from '../util/articles_api_util';
import {
ArticleConstants
} from '../actions/article_actions';
const ArticlesReducer = (state = {}, action) => {
switch (action.type) {
case ArticleConstants.RECEIVE_ALL_ARTICLES:
debugger
return merge({}, action.articles);
default:
return state;
}
};
export default ArticlesReducer;
Store
import { createStore, applyMiddleware } from 'redux';
import RootReducer from '../reducers/root_reducer';
import thunk from 'redux-thunk';
import * as APIUtil from '../util/articles_api_util';
export const ArticleConstants = {
RECEIVE_ALL_ARTICLES: "RECEIVE_ALL_ARTICLES",
REQUEST_ALL_ARTICLES: "REQUEST_ALL_ARTICLES"
}
Actions
export function fetchArticles() {
return function(dispatch) {
return APIUtil.fetchArticles().then(articles => {
dispatch(receiveAllArticles(articles));
}).catch(error => {
throw(error);
});
};
}
export const requestAllArticles= () => ({
type: REQUEST_ALL_ARTICLES
});
export const receiveAllArticles = articles => ({
type: RECEIVE_ALL_ARTICLES,
articles
});
const configureStore = (preloadedState = {}) => (
createStore(
RootReducer,
preloadedState,
applyMiddleware(thunk)
)
);
export default configureStore;
APIUtil
export const fetchArticles = (success) => {
$.ajax({
method: 'GET',
url: `/api/articles`,
success,
error: ()=> (
console.log("Invalid Article")
)
});
};
Arrow functions only do implicit returns if you leave off the curly braces. As soon as you include curly braces, you have defined a function body, and need to explicitly return a value.
Your fetchArticles function is written as an arrow function with curly braces. However, you are not explicitly returning the result of the $.ajax() call. So, the return value of the function is undefined, and there's no promise returned that you can chain off of.

React Redux async action testing

I have my test written to test async actions. I'm currently getting the following error TypeError: Cannot read poperty 'then' of undefined and it is pointing to the following line in my code
return store.dispatch(actions.fetchMovies()).then(() => {
Here is my code :
async actions test :
import { createStore, applyMiddleware } from 'redux';
import initialState from '../reducers/initialState';
import rootReducer from '../reducers/index';
import thunk from 'redux-thunk';
import * as actions from './actions';
import * as ActionTypes from '../constants/constants';
import nock from 'nock';
import { expect } from 'chai';
import API_KEY from '../config/config';
const MOVIES_API = 'https://api.themoviedb.org/3/discover/movie?api_key='+API_KEY;
describe('async actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('creates FETCH_MOVIES_SUCCESS when fetching movies is complete', () => {
nock(MOVIES_API)
.get()
.reply(200, {data: {results: [{title: 'Batman vs Superman'}]}});
const expectedActions = [
{ type: ActionTypes.FETCH_MOVIES },
{ type: ActionTypes.FETCH_MOVIES_SUCCESS, data: {results: [{title: 'Batman vs Superman'}]}}
];
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
return store.dispatch(actions.fetchMovies()).then(() => {
expect(store.getActions()).to.deep.equal(expectedActions);
});
});
});
actions:
import axios from 'axios';
import * as constants from '../constants/constants';
import API_KEY from '../config/config';
export const fetchMovies = () => {
const MOVIES_API = 'https://api.themoviedb.org/3/discover/movie?api_key='+ API_KEY;
return dispatch => {
dispatch({
type: constants.FETCH_MOVIES
});
axios.get(MOVIES_API).then(function(response) {
dispatch({
type: constants.FETCH_MOVIES_SUCCESS,
data: response.data.results
});
})
.catch(function(res) {
dispatch({
type: constants.FETCH_MOVIES_ERROR,
msg: res.message
});
});
};
};
This is the first time testing async actions so I'm not sure what's going wrong.
It's because your action doesn't return a promise - change your action to return a promise that can be awaited. This isn't required, but if you want to know when your API call has completed (i.e. your unit test wants to know in this particular case), then you can return a promise as a convenience side effect of the action:
export const fetchMovies = () => {
const MOVIES_API = 'https://api.themoviedb.org/3/discover/movie?api_key='+ API_KEY;
return dispatch => {
dispatch({
type: constants.FETCH_MOVIES
});
// Return a promise
return axios.get(MOVIES_API).then(function(response) {
dispatch({
type: constants.FETCH_MOVIES_SUCCESS,
data: response.data.results
});
})
.catch(function(res) {
dispatch({
type: constants.FETCH_MOVIES_ERROR,
msg: res.message
});
});
};
}
;
Try using redux-mock-store instead of redux createStore(). This is a mock store for testing async action creators and middleware. The Github page also includes some examples how to use it.
EDIT:
What happens when you modify your action creator so that it returns the result of axios.get(MOVIES_API)?

Categories