Apollo-client: ApolloClient: ObservableQuery.map/.filter/.reduce/.flatMap don't work

Created on 10 Sep 2019  ·  8Comments  ·  Source: apollographql/apollo-client

Intended outcome:
I'd like to use the basic zen-observable methods like map and filter to pipe the results of a query through some transformations before subscribing to it.

apolloClient.watchQuery({ query })
  .map(result => transformResult(result))
  .subscribe(console.log);

The docs say "Refer to the zen-observable documentation for additional context and API options." — I read that as the standard zen-observable API should be available and cooperate with the ObservableQuery API.

Actual outcome:

Calling any of the above methods cause runtime errors that look like:

/sandbox/node_modules/apollo-client/bundle.umd.js:146
      _this.variables = options.variables || {};
                                ^

TypeError: Cannot read property 'variables' of undefined

The actual line of code where the error occurs is here.

How to reproduce the issue:

Reproduction:
https://codesandbox.io/embed/apollo-client-observable-query-map-509cw

zen-observable seems to support subclassing, but it assumes that the subclass constructor has the same signature. This creates two problems:

  1. When .map creates a new ObservableQuery, it doesn't know about the constructor's queryManager, options, and shouldSubscribe arguments.
  2. Even after I hacked in support for new ObservableQuery(fn), the new observable returned from .map doesn't have a query manager so ObservableQuery's methods like refetch do not work.

Versions

[email protected]

has-reproduction 🐞 bug 🚧 in-triage

Most helpful comment

What works for me:

````js
import Observable from 'zen-observable';
// client built from from 'apollo-client' not 'apollo-boost';

const obs = client.subscribe({
query: gql(queryString),
variables,
...opts
});
const obs2 = Observable.from(obs).map((e) => transform(e));
obs2.subscribe({
next,
error,
complete
});
````

All 8 comments

Okay, quick note here:

  1. We definitely need to add unit tests to the ObservableQuery implementation. I'm hoping to add those and submit a PR soon.
  2. Because ObservableQuery doesn't accept an observer as its first argument, I'm not sureextension is the right way to go here. To your point (https://github.com/zenparsing/zen-observable/blob/f63849a8c60af5d514efc8e9d6138d8273c49ad6/src/Observable.js#L252-L254) isn't expecting anything but an observer.
  3. Composition might be the answer, or perhaps there's a simpler change that I'm missing ATM. I'll revisit with the team this week.

To add some more color to this ticket: I _can_ use RxJS's from to get access to map and friends:

const { from } = require('rxjs');
const { map } = require('rxjs/operators');

from(apolloClient.watchQuery({ query }))
  .pipe(map(transformResult))
  .subscribe(console.log);

So that's a nice, albeit heavy, solution. It's still problematic that I lose access to the ObservableQuery API tho:

from(apolloClient.watchQuery({ query, variables }))
  .pipe(map(transformResult))
  .refetch(newVariables); // TypeError: mapped.refetch is not a function

Can I pick up this bug?

@vilvaathibanpb Please feel free to give it a go! We've been discussing how best to solve this one, but have been focused on the next version of Apollo Client. If we're going to address it, we'll need to fix it in the 3.x version at this point.

@jhampton Sure, I will give a try on this :)

One workaround I just found:

import Observable from 'zen-observable';

Observable.from(apolloClient.watchQuery({ query }))
  .map(result => transformResult(result))
  .subscribe(console.log);

This removes the ObservableQuery API, so if you need refetch, you can do:

const original = apolloClient.watchQuery({ query });
const transformed = Observable.from(original)
  .map(result => transformResult(result));

transformed.subscribe(console.log);
original.refetch({ some: 'variables' });

What works for me:

````js
import Observable from 'zen-observable';
// client built from from 'apollo-client' not 'apollo-boost';

const obs = client.subscribe({
query: gql(queryString),
variables,
...opts
});
const obs2 = Observable.from(obs).map((e) => transform(e));
obs2.subscribe({
next,
error,
complete
});
````

@hwillson Do we have a sense if this might be address in AC3?

Was this page helpful?
0 / 5 - 0 ratings