0

I'm having a hard time getting flatMap to work with Rx.DOM.getJson() some of the time and can't figure out what I'm doing wrong.

html

<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs-dom/7.0.3/rx.dom.js"></script>
<script src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xta1/t39.3284-6/12624096_747368668729465_1053913233_n.js"></script>
<script src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xaf1/t39.3284-6/12624065_1518596455100766_427485197_n.js"></script>
  <title>JS Bin</title>
</head>
<body>
  <button id='fetch'>Fetch</button>
  <div id="content"></div>
</body>
</html>

javascript

// Actions
const Actions = {
  LoadPosts: 'LoadPosts'
};

// Disptacher
const Dispatcher = new Rx.Subject();

function send(action) {
  Dispatcher.onNext({action: action});
}

// Stores
const url = 'http://jsonplaceholder.typicode.com/posts';

const FetchedPosts$ = Dispatcher
  .filter(x => Actions.LoadPosts === x.action)
  .do(x => console.log(x))
  .flatMap(() => {
    console.log('flat');
    return Rx.DOM.getJSON(url);
  })
  .do(x => console.log('after', x));

// Views
const fetchButton = document.getElementById('fetch');
const Fetch$ = Rx.Observable.fromEvent(fetchButton, 'click')
  .do(x => send(Actions.LoadPosts));

const PostsView$ 
  = FetchedPosts$.map(posts => posts.map(post => <li>{post.title}</li>));

const RootView$ = Rx.Observable.merge(Fetch$, PostsView$);

RootView$.subscribe(
  content => ReactDOM.render(
    <ul>{content}</ul>, 
    document.getElementById('content')
  )
);

output

[object Object] {
  action: "LoadPosts"
}
"flat"
"Script error. (line 0)"

So the output shows that I'm getting into the flat map and if I pull out the Rx.DOM.getJSON(url) and subscribe to it then I can write out the value that gets returned. However, from in the flatMap nothing returns and I'm seeing an error in the network tab showing that Rx is canceling the request as shown below.

enter image description here

Here's a JsBin: http://jsbin.com/geruzo/edit?js,console,output

Edit I just noticed that removing react allows the Rx.DOM call to succeed.

Ryan Vice
  • 2,133
  • 3
  • 23
  • 33

1 Answers1

1

Ok, I will answer by referring to your jsbin code, which is a different than the posted code, but was easier for me to check.

First of all, a fixed version of your code could be found here: http://jsbin.com/yugener/edit?html,js,output

Your first problem is that the "LoadPosts" action was fired on every render, without having to click the button, which caused an endless render loop. Render->LoadPosts->Render->LoasPosts...

// The send will just be executed when the component is rendered
<button click={send(Actions.LoadPosts)}>fetch</button>

It should be (also onClick, instead of click):

<button onClick={() => send(Actions.LoadPosts)}>Fetch</button>

Since there was a render loop, and "LoadPosts" action kept on firing, Rx.DOM.getJSON was called again before the previous request has finished, and therefore it would cancel the previous request. You would than get an error thrown (with the request aborted etc.), and catch it:

.flatMap(() => Rx.DOM.getJSON('http://jsonplaceholder.typicode.com/posts'))
.catch(error => console.log('error', error))

In RXJS the catch operator expects you to return an observable, and since you didn't, undefined was returned, which in turn raised the following error:

Uncaught TypeError: Cannot read property 'subscribe' of undefined

As a side note, in your case instead of using flatMap, you should use switchMap (alias flatMapLatest), since everytime the "LoadPosts" action is fired, you want to ignore all previous requests, and get data only from the last request. For more information on the difference between the two: flatMap vs switchMap. In cases where you might want to abort previous requests by design, flatMap would also avoid getting the "aborted" error, since the previous observable is no longer emitting values.

Community
  • 1
  • 1
omerts
  • 8,485
  • 2
  • 32
  • 39
  • Thanks @omerts for the detailed reply. Do you have any details about a good pattern for logging errors in .catch()? – Ryan Vice May 20 '16 at 11:39
  • We are also looking for the best pattern. But for now, in the catch(), you could either log and return Observable.empty(), or log and return an observable which will emit an error object, and show some error in the UI. – omerts May 22 '16 at 08:38