Apollo + Meteor: which features exactly are coming in 1.5?

You can write new publish/subscribe calls with a Meteor method instead. Call the method and store the static data somewhere, eg… setState, redux, local minimongo collection… whatever you’re using today. GraphQL mutations are very similar. But as Sashko said… you really don’t have to do anything today to prepare for it, but making Meteor methods may save you a bit of time.

1 Like

I disagree - I think using Apollo is much more like pub/sub than methods. A big part of the goal is to save people from writing manual data loading code with redux.

4 Likes

Yeah I totally agree with the end goal not having to do a lot of manual loading code. I just meant that i’ve found it easier to migrate a Meteor method to a GraphQL mutation. I def. wouldn’t refactor current pub/sub code to methods and manual loading but if someone needed a bridge than that’s saved me a lot of effort. However, in the big scope of things I haven’t found it hard to switch a page over to 100% Apollo either.

FWIW Apollo Client has saved me from all that Redux wiring ceremony… @sashko thank you x100 !!! I’ve found that I only use Redux for truly global state (modals, etc…) and I can use Apollo and local state 99% of the time which makes for really cohesive code :grinning:

4 Likes

fyi: co·he·sion: the action or fact of forming a united whole.

Apollo makes for united under whole code…

1 Like

What about deploying Apollo Servers to Galaxy?

Right now, only Meteor-based servers can be deployed to Galaxy.

If you read deeply into the end of this blog post, you’ll find that Galaxy infrastructure has been upgraded to potentially support arbitrary containers. We’ll see if that might include plain Node apps soon, with a simple Meteor-independent deploy tool; it depends on what customers are asking for.

6 Likes

Awesome, that’s very exciting.

It makes a lot of sense to be able to be able to deploy pure node apps to Galaxy with a global NPM package, especially if Meteor is going to be converted into NPM modules moving forward.

It’d also be really interesting to have the Galaxy console categorize its services, similar to how the AWS console categorizes its services. For example “Node.js - Apollo - Meteor” and MDG can continue to build on those services and add more in the future, right to the “console” (again, like AWS or Google Cloud) until it has the whole deployment and infrastructure figured out, making web development easy and hassle free.

Yikes… my mouth is watering at the thought of it. :grin:

3 Likes

Hi, interesting comment, care to elaborate? If I understand their correctly, you use Apollo react to load data into your component and then you use stateful react components to maintain state?

1 Like

Ok I’ll bite :smile:

So previously I went in the direction of having all stateless components and one single source of in memory state. This state (redux) would hold local state for keeping track of spinners, tab positions, pretty much everything. One of the really nice parts about this that it’s easy to test the views again because they just take in props (from Redux’s connect) and they call an action creator function which can be stubbed out in tests easily.

In theory this is nice and you have this trail of actions streaming through your app but in reality it was a bit of wiring or “ceremony” for local state and a lot of wiring and boilerplate for getting server data… especially with handling errors. Now the actually ajax call is outside of the view and the only way to get the error back into the view is to send more actions. In theory this is ok but practically it’s a pain in the butt (still better than the Blaze spaghetti mess of 2012 area).

Now with GraphQL and Redux it’s about the same amount of wiring as AJAX/REST and Redux but you get a little bit of a code reduction and can usually use fragments (depending on the client library). Fragments allow you to re-use fields and make GraphQL less verbose. It also co-locates the fields to fetch right next to the view (same file).

With Apollo you can forget about the Redux cache for all server related state. You can just keep making calls and the first time will hit the server and by default future queries will read from the cache. To sync the cache you can return the new data in a mutation response (forcing views to update automatically).

To tie these together, using containers to separate data concerns and handle state (via setState) means you can get those nice clean views back because they’re now just taking props. You can define functions in the container and pass them down, keeping it testable and clean. This also make it easy for designers!

I’ve been using this for approx 3 months and it’s been working out really really well. It actually makes React fun again!

Example of a container and view component (note, a container isn’t required for apollo, i just prefer the organization). I’ve also left out the mutation to keep the example shorter:


// app/pages/post/index.js
const Container = React.createClass({
  viewerOwnsPost() {
    return this.props.data.post.userId === _viewerId;
  },

  handleEditClick(postId) {
    // use a closure to capture post id and return a new func for the click handler
    // I use this technique for a list of posts however here we could just use props.post.id since there is only 1
    return (event) => {
      // call Apollo mutation to edit data
    }
  },

  // do data manipulation here and pass down as props
  
  render(props=this.props) {
    const {post, loading, error} = this.props.data;
    if (error) {
      AlertError(error);
      return null;
    }
    return (
      <Post
        {...post} 
        loading={loading}
        viewerOwnsPost={this.viewerOwnsPost}
        />;
    );
  }
});

export default graphql(gql`
  query getPost  {
    posts(id: "p123") {
      id
      title
      desc
    }
  }
`)(Container);



// app/pages/post/post.js

const Post = (props) => {
  if (props.loading) return <LoadingSpinner />;
  return (
    <View style={styles.container}>
      <Text style={styles.title}>{props.title}</Text>
      <Text style={styles.desc}>{props.desc}</Text>
      {props.viewerOwnsPost &&
        <Link onClick={props.handleEditClick(props._id)} style={styles.title}>
          Edit Post
        </Link>
      }
    </View>
  );
};

// throw warning if props are not used in dev
Post.propTypes = {
  title: React.PropTypes.string.isRequired,
  viewerOwnsPost: React.PropTypes.func.isRequired,
};

const styles = StyleSheet.create({
  ...
});

export default Post;
9 Likes

Fantastic! Thanks for that! One last question … how does a view know to update data after your mutation? Do you just call refetch? That seems like an overkill. You mention that this can happen by returning data from mutation. But how do you wire the change to the component?

1 Like

Indeed, manually refetching would be a hassle. You can do this at will when you need to (like a refresh button on a mobile app).

However, your mutation has to return something (I think at least) and most of the time you would return the data it’s changed. When you do this you want to return the _id of the document so that Apollo can merge the old object with the new one you just returned. The react-apollo binding take care of re-rending the view whenever the apollo cache has changed.

// in click handler
      this.props.mutate({ variables: { text: "task", list_id: 1 } })
        .then(({ data }) => {
           // use it here if you like, this will also update the redux cache
          console.log('got data', data);
        }).catch((error) => {
          console.log('there was an error sending the query', error);
        });  

Note you can also call this from outside the view when needed. Sometimes i’ll want to preload the “feed” data right after logging in so I’ll manually fetch the minimum for that, then hide the spinner and redirect, to make the view feel “fast”.

5 Likes

I agree with this 100%. If you’re getting ready for Apollo then writing methods over pubsub is well worth the investment. Migrating old code from methods to Apollo has been super nice. Still struggling with bugs in migrating pubsub.

The root cause for us was tracker and subscribes and all this jazz are hard to keep predictable. So just opens up a can of worms when moving it from pubsub which had its own set of characteristics.

With methods it’s quite predictable already so the migration was very 1 to 1.

My 2 cents

2 Likes

Good to see both perspectives! Just wanted to chime in and say all of the stuff I have personally seen moved in our internal apps was pub/sub, but it’s good to know that migrating methods is easy as well.

2 Likes

Maybe some kind of Mongo package driver for GraphQL like this one: https://github.com/RisingStack/graffiti-mongoose

Why not just use Mongoose just like here?

Yeah personally I think it’s a good idea to separate the API from the database models.

1 Like

For what it’s worth I think it might be nice to use Apollo with minimongo. I would have no problem with running GraphQL queries and depositing them into local collections. Maybe minimongo can be rebranded as a reactive cache and maybe the two actions could be integrated somehow.

5 Likes

I haven’t looked into Apollo too much. but here’s how I’d guess it should look in Meteor:

// run query
UserList = new Apollo.query('add-query-here')

// returns reactive data to template
UserList.data().fieldName 

// re-run Query
UserList.update()
2 Likes

Yep, this is totally do-able. I wonder if you would want to wrap the GraphQL call with a function that will handle inserting the local collection? That might clean up some of the verboseness with Blaze. maybe something like the code below? (though if one is using React the apollo-react bindings are more simple since it has it’s own cache).

I think trying to shoehorn mutations into the minimongo API would be awkward since you tend to think differently about data.

Categories = new Mongo.Collection(null);

function getCategory() {
  client.query({
    query: gql`
    query getCategory($categoryId: Int!) {
      category(id: $categoryId) {
        name
        color
      }
    }
  `,
    variables: {
      categoryId: 5,
    },
    forceFetch: true,
  }).then(({ data }) => {
    Categories.upsert(data._id, data);
  }).catch((error) => {
    console.log('there was an error sending the query', error);
  });
}

I don’t know after writing this out maybe a better approach would be to create some kind of Blaze bindings to get a more similar API like apollo-react?

2 Likes

What is that? Since when porps mutable?