martyjs / marty

A Javascript library for state management in React applications
http://martyjs.org
MIT License
1.09k stars 77 forks source link

Empty res.json() with valid res.body #358

Closed command-tab closed 8 years ago

command-tab commented 8 years ago

Hi folks,

I'm using Marty 0.10.4, and I'm attempting to follow the recommendations for removing dependence on the automatic parseJSON call, but I'm seeing a strange behavior: When my valid data source requests finish with 200 OK, calling res.json() returns an empty object {}.

I'm calling Marty.HttpStateSource.removeHook('parseJSON'); in my server.js to remove the automatic parseJSON hook, then in my data source, doing a simple GET:

class ThingSource extends Marty.HttpStateSource {
  getThing(id) {
    return this.get(`/api/things/${id}`).then((res) => {
      console.log(res.body); // returns { id: 42, ... } with parseJSON hook, { _opts: {}, ... } without
      console.log(res.json()); // always returns {}
      if (res.ok) {
        return res.json();
      }
      throw new Error('Failed to get thing');
    });
  }
}

If I temporarily omit the removal of the parseJSON hook, res.body is in fact the JSON text that I expect to parse by calling res.json(), so I can see that my requests are succeeding. I just can't figure out why, in either case, res.json() always just returns {}.

Am I missing something?

Thanks so much!

taion commented 8 years ago

The body on a request is a readable byte stream that should only be consumable once anyway. Do you still see res.json() returning empty results if you comment out the console.log(res.body) line?

command-tab commented 8 years ago

Hi @taion, thanks for responding so quickly :)

Even when I log only res.json(), I still get {}. Is there any way I can dig deeper into what's happening under the hood?

Thanks!

taion commented 8 years ago

res.json() actually returns a promise - you'd have to do e.g. res.json().then(json => console.log(json)).

command-tab commented 8 years ago

Aha! That worked — that successfully logged out the data I'm expecting. Hmm, so maybe the API calls are working correctly, and the fact that my Marty container's failed method is being called is the result of something else.

command-tab commented 8 years ago

Interesting... If I dig into the errors passed to the failed Marty container method, I see...

{"things":{"name":"Not found","message":"Not found","status":404}}

...despite the fact that my API method is returning 200 OK, with data, as application/json.

codyhatch commented 8 years ago

The process for a fetch is:

  1. Hit the local handler. If it returns undefined then...
  2. Hit the remote handler.
  3. Hit the local handler again. If it returns undefined then...
  4. Trigger a failed state on the fetch.

So presumably your issue that the local handler is still returning undefined even after the remote API call completes. Obvious causes:

  1. Your remote handler isn't causing the store to update correctly. eg, your dispatching an event but nothing in the store is handling it, or the handler in the store is storing the new data under the wrong key.
  2. Your local handler isn't fetching data from the store correctly, eg, it's looking for data under the wrong key.

I'd suggest looking at your store's state after the failure. Is the remote data actually in the store?

command-tab commented 8 years ago

OH, that's totally it!

I was intentionally returning undefined from my locally handler to force invocation of the remotely handler, with the thinking that I would get remote data working first, then worry about storing it, then get it to my component. Turns out, Marty is smarter than I assumed it was.

This is great! Thanks @codyhatch and @taion!