BeniaminoBaggins BeniaminoBaggins - 1 year ago 73
React JSX Question

Redux-observable POST ajax request never sends the network request

I have this redux-observable epic that

POST
s a json body to my backend, but it never actually gets so far as to send the actual network request. It uses Rx.DOM.Request.post(url, [body]) (I think? please view my imports to confirm). I can verify that the network request never makes it out of my app, let alone hits my backend:

import { Observable } from 'rxjs'
import 'redux-observable'
import {
UPLOAD_IMAGE,
UPLOAD_IMAGE_FULFILLED,
UPLOAD_IMAGE_REJECTED,
UPLOAD_PRODUCT,
uploadImageFulfilled,
uploadImageRejected,
uploadProductFulfilled,
uploadProductRejected
} from './addForm.action'
import {
updateAlertModalIsOpen,
updateAlertModalMessage,
updateAlertModalTitle
} from '../alert-modal/alertModal.action'
import 'rxjs/add/operator/catch'
import { RNS3 } from 'react-native-aws3'
import { ajax } from 'rxjs/observable/dom/ajax'

export const uploadProductEpic = action$ =>
action$.ofType(UPLOAD_PRODUCT)
.mergeMap(action => {
ajax.post(
'http://192.168.20.3:4000/products',
action.payload,
{ 'Content-Type': 'application/json' })
})
.map(response => uploadProductFulfilled(response))
.catch(error => Observable.of(uploadProductRejected(error)))


It gets invoked by a button press:

onPress={() => {
let product = {
"name": props.name,
"brand": props.brand,
"description": props.description,
"image": "fakeUrl"
}

props.uploadProduct(JSON.stringify(product))


this is the contents of
action
object in the above epic:

payload: "{"name":"v","brand":"v","description":"v","image":"fakeUrl"}"
type: "UPLOAD_PRODUCT"


Is it because my json is using all double quotes with no escapes?

I can verify the line of code in the epic that performs the ajax post does run when pressing the button, and the on the first button press it ends up dispatching the
uploadProductRejected(error)
, without even making the network request, and where error that gets dispatched in the
uploadProductRejected
action is just
{ }
and on subsequent button presses it never even dispatches a rejected or fulfilled action.

Any idea why it is rejected, and why it never even attempts to make the network request?

Answer Source

btw--the documentation you point to for Rx.DOM.Request is for v4 of RxJS, but your code is using v5. Documentation for v5 is located here however Observable.ajax is not yet documented unfortunately but it's pretty close to the v4 version and has a mostly obvious API.


Regarding your code, there are a couple housekeeping things we need to do first.

You probably shouldn't put the map and catch outside on the top-level Observable. If you do, the any inside your mergeMap is allowed to propagate our and reach the action$.ofType, meaning your epic will terminate because you catch the error and instead switch from the errored out top-level chain to Observable.of(uploadProductRejected(error)).

Instead, you want to isolate your Observables by placing that logic inside the mergeMap:

export const uploadProductEpic = action$ =>
  action$.ofType(UPLOAD_PRODUCT)
    .mergeMap(action => {
      ajax.post(
        'http://192.168.20.3:4000/products', 
        action.payload,
        { 'Content-Type': 'application/json' }
      )
        .map(response => uploadProductFulfilled(response))
        .catch(error => Observable.of(uploadProductRejected(error)))
    });

While this is just normal RxJS, in redux-observable this is particularly important since you don't want your epic to terminate when one request fails. This is described in the docs here.


Now that we have that out of the way, to truly solve your problem you need to look at what the actual error message is that you caught and emitted as uploadProductRejected().

Although you didn't provide it, I can guess based on your code that it is something like:

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable. at subscribeToResult

It would also include a stack trace, which should first point to something like MergeMapSubscriber._innerSub, which means it's the mergeMap operator that is throwing this error. If we look at your usage of mergeMap we can see that in fact we are not returning anything! Since you're using an arrow function with curly braces that means you need to have an explicit return.

export const uploadProductEpic = action$ =>
  action$.ofType(UPLOAD_PRODUCT)
    .mergeMap(action => { // <--------------------------- whoops!
      ajax.post(
        'http://192.168.20.3:4000/products', 
        action.payload,
        { 'Content-Type': 'application/json' })
    })

You probably meant to use an arrow function without curly braces, so that it has an implicit return.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download