Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

0.4.0 #120

Merged
merged 0 commits into from Aug 12, 2016
Merged

0.4.0 #120

merged 0 commits into from Aug 12, 2016

Conversation

jbaxleyiii
Copy link
Contributor

@jbaxleyiii jbaxleyiii commented Jul 24, 2016

This PR is a rebuild of the api for react-apollo. The initial discussion can be found here

API overview

react-apollo exposes three top level items for the client (and two for the server) which, when used in conjunction, make for easily binding graphql actions to react components. The intent of this library is to make co-location of graphql data simple, and mutations easy to use within components.

ApolloProvider

Modeled after react-redux, this is a component which binds the created client from ApolloClient to the store. It can be used as a drop in replacement for the Provider from react-redux, or used in with it.

Basic apollo version:

import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';

const client = new ApolloClient();

ReactDOM.render(
  <ApolloProvider client={client}>
    <MyRootComponent />
  </ApolloProvider>,
  rootEl
)

Used in conjunction with a redux store:

import { createStore, combineReducers, applyMiddleware } from 'redux';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';

import { todoReducer, userReducer } from './reducers';

const client = new ApolloClient();

const store = createStore(
  combineReducers({
    todos: todoReducer,
    users: userReducer,
    apollo: client.reducer(),
  }),
  applyMiddleware(client.middleware())
);

ReactDOM.render(
  <ApolloProvider store={store} client={client}>
    <MyRootComponent />
  </ApolloProvider>,
  rootEl
)

Used in conjunction with react-redux Provider:

import { createStore, combineReducers, applyMiddleware } from 'redux';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { Provider } from 'react-redux';

import { todoReducer, userReducer } from './reducers';

const client = new ApolloClient();

const store = createStore(
  combineReducers({
    todos: todoReducer,
    users: userReducer,
    apollo: client.reducer(),
  }),
  applyMiddleware(client.middleware())
);

ReactDOM.render(
  <Provider store={store}>
    <ApolloProvider client={client}>
      <MyRootComponent />
    </ApolloProvider>
  </Provider>,
  rootEl
)
## `withApollo`

withApollo is a HOC higher order component which provides access to your apollo-client as a prop to your wrapped component. This is useful if you want to do custom logic with apollo, without using the graphql HOC.

const MyComponent = withApollo((props) => {
  // this.props.client is the apollo client
  return <div></div>
})

// or

@withApollo
class MyComponent extends Component {
  render() {
    // this.props.client is the apollo client
    return <div></div>
  }
}

graphql

graphql is a HOC higher order component which allows you to declare a graphql operation (mutation or query) and have the data or actions it represents bound to the wrapped component. For queries, this means graphql handles the fetching and updating of information from the query using apollo's watchQuery method. For mutations, graphql binds the intended mutation to be called using apollo.

The signature of graphql is as follows:

graphql(Document, options)(Component)

Lets break that apart..

HOC

graphql(/* arguments */)(Component) in keeping with the HOC model, graphql should be used with either a react class, or a stateless component. It should wrap the component using either function syntax or as a decorator. For example:

// function syntax
const MyComponent = (props) => (
  <div></div>
)

const MyComponentWithData = graphql(Document)(MyComponent);


@graphql(Document)
class MyDecoratedComponent extends Component {
  render() {
    return <div></div>
  }
}

Both of the above will return a wrapped component which will get the benefit of graphql integration.

Document

The first, and only required, argument of graphql is a graphql document. This cannot be a string so using a library like graphql-tag or a compilation step is recommended. This can either be a query or a mutation but not both.

import { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

@graphql(gql`
  query getUser {
    user { name }
  }
`)
class MyDecoratedComponent extends Component {
  render() {
    const { loading, user } = this.props.data;

    if (loading) return <h1>Loading...</h1>
    return <h1>{user.name}</h1>
  }
}

Options:

The options argument allows for customizing the options of the operation, and customizing the shape of the child component. It has the following signature:

{
  options: (props) => QueryHandleOpts || MutationHandleOpts,
  props: (result) => result,
  name: string
}

options

The options method allows you to map the props passed from the parent component to create either options for watchQuery or mutate. The default is to pass props as the variables key in both option types ((props) => ({ variables: props })).

if you are coming from the previous react-apollo where state was useable, this is where you can use graphql in concert with connect from react-redux

Using the default method will expect an id key to exist on the props:

import { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

@graphql(gql`
  query getUser(id: $ID!) {
    user { name }
  }
`)
class MyDecoratedComponent extends Component {
  render() {
    const { loading, user } = this.props.data;

    if (loading) return <h1>Loading...</h1>
    return <h1>{user.name}</h1>
  }
}

Using a custom mapping method which returns a computed option definition

import { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

@graphql(gql`
  query getUser(id: $ID!) {
    user { name }
  }
`, { options: (props) => ({ variables: { id: props.userId } }) })
class MyDecoratedComponent extends Component {
  render() {
    const { loading, user } = this.props.data;

    if (loading) return <h1>Loading...</h1>
    return <h1>{user.name}</h1>
  }
}

Passing other option definitions

import { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

@graphql(gql`
  query getUser(id: $ID!) {
    user { name }
  }
`, { options: () => ({ pollInterval: 1000 }) })
class MyDecoratedComponent extends Component {
  render() {
    const { loading, user } = this.props.data;

    if (loading) return <h1>Loading...</h1>
    return <h1>{user.name}</h1>
  }
}
#### `props`

The props method allows you to customize the props passed to the child component based on resulting data. It takes a method which receives the result object (which includes the data, the loading state, any errors, and client methods) and expects an object in return. This is similar to react-redux's mapStateToProps method.

import { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

@graphql(gql`
    query getUser(id: $ID!) {
      user { name }
    }
  `,
  {
    props: ({ data }) => {
      if (data.loading) return { userLoading: true };
      if (data.error) return { hasErrors: true };
      return {
        currentUser: data.user,
        refetchUser: data.refetch,
      };
    },
  }
)
class MyDecoratedComponent extends Component {
  render() {
    const {
      userLoading,
      hasErrors,
      currentUser,
      refetchUser
    } = this.props;

    if (userLoading) return <h1>Loading...</h1>
    return <h1>{currentUser.name}</h1>
  }
}

name

If you want to customize the name of the resulting props (instead of data or mutate) you can use a custom name via the name field on the options. If you pass a prop method, it will overwrite this name. This is particularly helpful for multiple operations on a single component.

Queries

Using graphql with queries makes it easy to bind data to components. As seen above, graphql will assign the result of the query as data to the props passed to the wrapped component. The shape of the result will be the following:

  1. loading: Boolean
    Loading will be true if a query is in flight (including when calling refetch)

  2. error: ApolloError
    The error key will be null if no errors were created during the query

  3. ...queries
    graphql will merge the root queries used within the query document executed. This allows for multiple root queries to be located on the top level of the result. For instance:

    query getUserAndLikes(id: $ID!) {
      user(userId: $id) { name }
      likes(userId: $id) { count }
    }

    will return a result object that includes { data: { name: "James" }, likes: { count: 10 } }.

  4. ...QuerySubscription
    The subscription created on this query will be merged into the passed props so you can dynamically refetch, change polling settings, or even unsubscribe to this query.

  5. query
    Sometimes you may want to call a custom query within a component. To make this possible, graphql passes the query method from ApolloClient as a prop

  6. mutate
    Sometimes you may want to call a custom mutation within a component. To make this possible, graphql passes the mutate method from ApolloClient as a prop

Mutations

Using graphql with mutations makes it easy to bind actions to components. Unlike queries, mutations return only a single method (the mutation method) to the wrapped component. When calling a mutation, you can pass an options that can be passed to apollo-client mutation. Mutations will be passed to the child as props.mutate

Previously react-apollo tried to match the shape of mutation props with that of query props. I don't think that was super helpful so mutation props match the need of mutations.

import { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

@graphql(gql`
  mutation addTask($text: String!, $list_id: ID!) {
    addNewTask(text: $text, list_id: $list_id) {
      id
      text
      completed
      createdAt
    }
  }
`)
class MyDecoratedComponent extends Component {

  onClick = () => {
    this.props.mutate({ variables: { text: "task", list_id: 1 } }) // pass in extra / changed variables
      .then(({ data }) => {
        console.log('got data', data);
      }).catch((error) => {
        console.log('there was an error sending the query', error);
      });
  }

  render() {
    return <h1 onClick={this.onClick}>Add Task</h1>
  }
}

Server methods

react-apollo supports integrated server side rendering for both store rehydration purposes, or fully rendered markup.

getDataFromTree

The getDataFromTree method takes your react tree and returns the context of you react tree. This can be used to get the initialState via context.store.getState()

// no changes to client :tada:

// server application code (custom usage)
import { getDataFromTree } from "react-apollo/server"

// during request
getDataFromTree(app).then((context) => {
  // markup with data from requests
  const markup = ReactDOM.renderToString(app);
});
## `renderToStringWithData`

The renderToStringWithData takes your react tree and returns the stringified tree with all data requirements. It also injects a script tag that includes window. __APOLLO_STATE__ which equals the full redux store for hydration. This method returns a promise that eventually returns the markup

// no changes to client :tada:

// server application code (integrated usage)
import { renderToStringWithData } from "react-apollo/server"

// during request
renderToStringWithData(app).then(markup => // send markup to client)

Server notes:
When creating the client on the server, it is best to use ssrMode: true. This prevents unneeded force refetching in the tree walking.

Client notes:
When creating new client, you can pass initialState: __APOLLO_STATE__ to rehydrate which will stop the client from trying to requery data.

@jbaxleyiii jbaxleyiii added this to the 0.4.0 milestone Jul 24, 2016
@jbaxleyiii jbaxleyiii self-assigned this Jul 24, 2016
@jbaxleyiii jbaxleyiii mentioned this pull request Jul 24, 2016
@tmeasday
Copy link
Contributor

tmeasday commented Jul 25, 2016

Looks great! combineRequests seems like a bit of an odd name to me though. I would preference just combine (it feels weird to include Request, which isn't really anywhere else in the API). If I was picking a word I'd choose Operation (so combineOperations).

@jbaxleyiii
Copy link
Contributor Author

@tmeasday combine sounds great to me! I'll edit the PR

@stubailo
Copy link
Contributor

I'd change the graphql function to something more descriptive, since that kinda intersects with some things inside the graphql-js package (probably nobody will run into it, but it seems a bit generic for a name).

Maybe something like connectGraphQL or graphQLContainer?

@stubailo
Copy link
Contributor

One thing missing from this API is a way to fetch a totally different query based on the props. Before, you could simply not fetch a query if a certain prop wasn't fetched, but now as far as I can tell you will always fetch the document and then only thing you can control is the options. Maybe that means we need a new option like skip: true or something if you don't need the query at all?

@stubailo
Copy link
Contributor

The default is to pass props as the variables key in both option types

AFAIK GraphQL servers error if they get unused variables - so I'm not sure if passing props as variables by default will be the right approach?

The subscription created on this query will be merged into the passed props so you can dynamically refetch, change polling settings, or even unsubscribe to this query.

These methods are on the observable now, just a heads up.

Unlike queries, mutations return only a single method (the mutation method) to the wrapped component.

I think it's important to be able to show a loading state while a mutation is in progress. What would be the way to do that here?


having said that, WOW this is super dope.

@jbaxleyiii
Copy link
Contributor Author

@stubailo

I'd change the graphql function to something more descriptive, since that kinda intersects with some things inside the graphql-js package

That sounds great to me. How do you feel about withGraphQL. React-router has withRouter which makes a lot of sense to me.

One thing missing from this API is a way to fetch a totally different query based on the props.

Ah yes, we actually run into this often. skip: true seems like a simple and effective solution.

AFAIK GraphQL servers error if they get unused variables

This wasn't clear enough in the api, my plan is to use variableDefinitions from the AST to try and auto match variables. I plan on erroring if you send props that the query can't execute (does apollo-client do this already?

These methods are on the observable now, just a heads up.

👍 I've got to play some catchup on all the awesome stuff in the client now!

I think it's important to be able to show a loading state while a mutation is in progress. What would be the way to do that here?

In practice, we have been using setState prior to calling the mutation to set a loading state then calling it again after the mutation. I'd be good to move it into react-apollo. I could always add it in on the mutation method (i.e. this.props.addTask.loading)

@davidwoody
Copy link

FWIW, between these two:

withRouter(Component) 
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(Component)

I would suggest that:

graphql([Document], [mapPropsToOptions], [mapResultToProps])(Component)

is most similar to the connect signature. So between withGraphQL vs connectGraphQL, it would seem that connectGraphQL could add clarity to the api expectations.

That said, I kind of like the simplicity of graphql since there isn't any pre-existing api expectations with that (at least not in my mind), and as a mini bonus it's less to type.

p.s. I'm so excited to start using this! Awesome stuff!

@stubailo
Copy link
Contributor

Yeah having a really short and sweet name can be nice :) people can always use ES2015 import renaming if they want to (not that most people are aware of that ES2015 feature though)

@jbaxleyiii
Copy link
Contributor Author

@stubailo are you okay with graphql then? ;)

@jbaxleyiii jbaxleyiii mentioned this pull request Jul 30, 2016
@jbaxleyiii
Copy link
Contributor Author

@stubailo how do you want me to handle documentation on this? Are the new docs ready?

@tmeasday @stubailo also, I'll probably abstract out the getDataFromTree as it isn't react-apollo specific anymore, but works for any component that exposes a static fetchData method.

@jbaxleyiii
Copy link
Contributor Author

@stubailo thoughts on the being the 1.0 (after a through RC testing) so align with the 1.0 of the client?

@jbaxleyiii jbaxleyiii changed the title [WIP] 0.4.0 [WIP] 1.0.0 Aug 2, 2016
@jbaxleyiii
Copy link
Contributor Author

The initial release of this is ready for testing!

The release candidate can be installed using npm i --save react-apollo@next

@stubailo
Copy link
Contributor

stubailo commented Aug 2, 2016

I don't think we are going to ship 1.0 of apollo-client until late september at the earliest, so I'd say it's a bit early. I'd rather ship all the features ahead of time, do a ton of testing, and then change the number at the last moment.

IMO 1.0 draws a lot of attention so you don't want to be making major changes in a 0.x -> 1.0 transition, it's mostly a marketing move.

@jbaxleyiii
Copy link
Contributor Author

Ah, that makes sense. I'll downgrade this back to the 0.4.0

@jbaxleyiii jbaxleyiii changed the title [WIP] 1.0.0 [WIP] 0.4.0 Aug 2, 2016
@tmeasday
Copy link
Contributor

tmeasday commented Aug 4, 2016

@jbaxleyiii just FYI have been looking at this and plan on digging a lot deeper very very soon.

@stubailo
Copy link
Contributor

stubailo commented Aug 4, 2016

@tmeasday perhaps we can try out the release candidate in Galaxy/GitHunt? If it works there then it should be pretty good

@tmeasday
Copy link
Contributor

tmeasday commented Aug 5, 2016

@jbaxleyiii. Took a closer look and worked on converting GitHunt over to the new API: https://github.com/apollostack/GitHunt/pull/72/files

First off, it's a big improvement!

Some comments / issues in order of immediacy:

  1. The behavior of mapResultsToProps doesn't match your example, or the intent I think. The return value still gets "scoped" to the query name, so I have to do this or to be clear:

    Profile.propTypes = {
      CurrentUserForLayout: React.PropTypes.shape({
        loading: React.PropTypes.bool.isRequired,
        currentUser: React.PropTypes.object,
      }),
    };

    Rather than what I want, which is

    Profile.propTypes = {
      loading: React.PropTypes.bool.isRequired,
      currentUser: React.PropTypes.object,
    };

    Ideally the presentational component (Profile) would not need to concern itself with where it's data is coming from (neither Apollo or specifics about the queries where the data came from).

  2. How do I pass extra arguments such as optimisticResponse and updateQueries into the mutation?

    Ideally this would happen in the container, not the presentational component.

    Perhaps the mutation form should take a second mapMutationsToProps argument also, so I can do something like:

    const CommentWithDataAndMutations = graphql(
      SUBMIT_COMMENT_MUTATION,
      // is this unintelligible ES2015?
      ({ submitComment }) => ({
         submitComment(args) {
           return submitComment(args, {
              optimisticResponse: ..../
           });
         }
      })
    )  
  3. @stubailo and I were talking about react-apollo and the other integrations and we think that there's perhaps some shared "stateful" behavior handling that could be shared and should perhaps be moved into apollo-client, enabling a more direct (and thus probably different again) API for react-apollo.

    I'll probably try and sketch something later this week/early next week to explain a little more, but I guess it's a little while away in the best case. But I wanted to give you a heads up.

@tmeasday
Copy link
Contributor

tmeasday commented Aug 5, 2016

Another thing:

    1. The arguments to mapResultsToProps appear to include variables, which is useful, but I'm seeing them alternate between being defined and undefined during the render process. Not sure what's happening there.

@tmeasday
Copy link
Contributor

tmeasday commented Aug 5, 2016

    1. There also appears to be a problem with fetchMore and createBoundRefetch -- the bound function gets called with a null result. I haven't investigated more but I'll list it here.

@jbaxleyiii
Copy link
Contributor Author

@tmeasday thanks so much for checking this out!

The behavior of mapResultsToProps doesn't match your example, or the intent I think

Good catch, the API design did not match the built product. I've adjusted the code to match the API as I agree, it is much nicer

How do I pass extra arguments such as optimisticResponse and updateQueries into the mutation?

The result of the mapPropsToOptions function allows you to do this. Detailed here

and I were talking about react-apollo and the other integrations and we think that there's perhaps some shared "stateful" behavior handling that could be shared and should perhaps be moved into apollo-client, enabling a more direct (and thus probably different again) API for react-apollo.

I'd love to know more, I really like this API and would love to know how it could be better / change with changes to the client

The arguments to mapResultsToProps appear to include variables, which is useful, but I'm seeing them alternate between being defined and undefined during the render process

Fixed and test added!

There also appears to be a problem with fetchMore and createBoundRefetch -- the bound function gets called with a null result.

This is actually a bug in the client. This should fix that. Once it does, I'll add a test here to ensure it works

@jbaxleyiii
Copy link
Contributor Author

@tmeasday @stubailo new code pushed as react-apollo@next

@stubailo
Copy link
Contributor

stubailo commented Aug 5, 2016

I really like this API and would love to know how it could be better / change with changes to the client

To clarify this, people often reach out to us for help because we have the Apollo Client core API where you call watchQuery, etc, and then the more declarative React API where you return static options from a function.

We were talking yesterday and it seems like if we added the concept of a "component" to the core, then react-apollo's job could simply be to initialize a "component" object, then that could have an idempotent function to basically set query options on it. Then react-apollo could basically call that, and it would be almost the same API as the "core".

But either way we don't think this needs to be done right away - just something we are going to think about in the next month.

@tmeasday
Copy link
Contributor

tmeasday commented Aug 8, 2016

@jbaxleyiii are you sure you published it? I'm not getting the fix in 1.0.0-rc.2, and 0.4.0 doesn't seem to exist.

Please ignore the above, total baloney.

@tmeasday
Copy link
Contributor

tmeasday commented Aug 8, 2016

Looking good @jbaxleyiii (check out https://github.com/apollostack/githunt/tree/react-apollo-next).

One final thing: how can we do a parameterized optimizedResponse?

You can see that we don't have access to the variables in the mutation here: https://github.com/apollostack/GitHunt/blob/react-apollo-next/ui/CommentsPage.js#L212-220

I would suggest a good solution to the problem might look like my 2. above? Or else maybe the variables could be passed to the mapPropsToOptions function?

@tmeasday
Copy link
Contributor

tmeasday commented Aug 8, 2016

@jbaxleyiii here is a little more color on what @stubailo was saying above. The final part is just my thoughts on what it could look like, and I love everyone's thoughts on it all.

apollographql/apollo-client#513

@tmeasday
Copy link
Contributor

@jbaxleyiii did you have any thoughts about the optimizedResponse question? Apart from that question, GitHunt is more or less integrated with 0.4.0 and we should move forward with shipping this thing!

@tmeasday
Copy link
Contributor

tmeasday commented Aug 11, 2016

@jbaxleyiii one minor thing that's kind of unfortunate is needing to use graphql(query, null, (result) => {...}) when you don't have variables to your query but you want to massage the query results.

I wonder if there's a way to get a nicer API for that use case (I also imagine it'll be really easy to leave out the null and stuff things up). Maybe we could also allow:

graphql(gql`...`, {
  mapPropsToOptions(props) { return {...}; }.
  mapResultsToProps({ loading, something }) { return { ... } )
});

(As an example, I was putting together an example for the apollostack website here: https://github.com/apollostack/frontpage-react-app/blob/ffab743608b9f3c95a09247f7a76d6f1d9b89c4e/src/App.js#L38)

@jbaxleyiii
Copy link
Contributor Author

@tmeasday you know I had been wondering the same thing. I think it's a great idea and it enforces the names of the methods which will make documentation and training better. I'll make that change today

@jbaxleyiii
Copy link
Contributor Author

@tmeasday I've adjusted the API (and the design of it above) to allow for an argument instead of positional arguments. It also allows you to pass an optimistic result as part of the mutation action (mutations now take a mutation option instead of just variables.)

Let me know if this solves the missing pieces!

@stubailo
Copy link
Contributor

@jbaxleyiii I don't know if you saw @tmeasday's question above:

One final thing: how can we do a parameterized optimisticResponse?

@jbaxleyiii
Copy link
Contributor Author

@stubailo yep! You can now pass in an optimisticResponse when calling the mutation which would allow you to create it with any variables you want

@jbaxleyiii
Copy link
Contributor Author

I did it this way so you could pass user input, component state, whatever as a response

@stubailo
Copy link
Contributor

Sounds great to me. Tom is out for a few minutes, when he comes back let's get a final OK and then we're probably ready to ship!

@jbaxleyiii
Copy link
Contributor Author

@stubailo @tmeasday and I were going to work on the new docs for shipping. I'd love to have them in place before the release

@stubailo
Copy link
Contributor

Oh OK, I'll let you guys figure it out!

@jbaxleyiii
Copy link
Contributor Author

API adjustments made, and added a test for redux usage in conjunction with graphql

@jbaxleyiii jbaxleyiii changed the title [WIP] 0.4.0 0.4.0 Aug 12, 2016
@jbaxleyiii
Copy link
Contributor Author

@tmeasday @stubailo I've added mobx, redux, and react-native testing so aside from documentation this is ready to go!

@tmeasday
Copy link
Contributor

@jbaxleyiii
Copy link
Contributor Author

Once docs are signed off, we can merge and release this.

cc @tmeasday @stubailo

@Vanuan
Copy link
Contributor

Vanuan commented Aug 12, 2016

So how would I provide both query and mutation to one component?

Say I have a form component

const MyForm = () => {
}

const MyComponentWithData = graphql(???)(MyForm);

Or you're saying that one should always have two separate components for queries and mutations?

const MyFormView = () => {
}

const MyComponentWithData = graphql(/* query */)(MyFormView);
const MyFormEdit = () => {
}

const MyComponentWithMutation = graphql(/* mutation */)(MyFormEdit);

How would I provide initial data to MyFormEdit?

@jbaxleyiii
Copy link
Contributor Author

@Vanuan you have a few options:

import React, { Component } from 'react';
import { graphql } from 'react-apollo';

class MyComponent extends Component { ... }
const MyComponentWithMutateAndData = graphql(UPVOTE)(graphql(DOWNVOTE)(MyComponent));

or with decorators

import React, { Component } from 'react';
import { graphql } from 'react-apollo';

@graphql(UPVOTE)
@graphql(DOWNVOTE)
class MyComponent extends Component { ... }

or with custom names:

import React, { Component } from 'react';
import { graphql } from 'react-apollo';

class MyComponent extends Component { ... }
MyComponent.propTypes = {
  upvote: React.PropTypes.func.isRequired,
  downvote: React.PropTypes.func.isRequired,
};

// This provides an `upvote` callback prop to `MyComponent`
const MyComponentWithUpvote = graphql(UPVOTE, {
  name: 'upvote',
})(MyComponent);

// This provides an `downvote` callback prop to `MyComponentWithUpvote`,
// and subsequently `MyComponent`
const MyComponentWithUpvoteAndDownvote = graphql(DOWNVOTE, {
  name: 'downvote',
})(MyComponentWithUpvote);

@Vanuan
Copy link
Contributor

Vanuan commented Aug 12, 2016

Ok, so it boils down to "use multi-layer wrapping". I see.

@jbaxleyiii jbaxleyiii merged commit 041f074 into master Aug 12, 2016
@zol zol removed the in progress label Aug 12, 2016
@jbaxleyiii jbaxleyiii deleted the 0.4.0 branch August 12, 2016 22:45
@stubailo
Copy link
Contributor

Guys this is amazing! congrats on the new release!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants