How to pass variables to a GraphQL query?

23.4k views Asked by At

Lets assume I have the following GraphQL query in React-Apollo

const CurrentUserForLayout = gql`
  query CurrentUserForLayout($avatarID: Int!) {
    currentUser {
      avatar_url(avatarID: $avatarID)
    }
  }
`;

const ProfileWithData = graphql(CurrentUserForLayout, {
  options: { variables: { avatarID: 1 } },
})(Profile);

Now if I want to let my React component Profile change the avatarID,

how would I do that?

I am new to React and GraphQl and I do not really understand the connection here:

graphql(CurrentUserForLayout, {
      options: { variables: { avatarID: 1 } },
    })(Profile);

Do I really need another parent component around ProfileWithData to pass another avatarID to the query? But what if the ID is manipulated by the Profile component, how do I let the Parent component know about that?

3

There are 3 answers

0
Robin Wieruch On BEST ANSWER

Before Apollo Client 2.1

Before Apollo Client 2.1, when you had to use Higher-Order Components (HOCs), there were two solutions which were pointed out correctly by henk in the other answer. I try to summarize them briefly:

  • Since the HOC has only access to the props, you need to introduce the changing variable in your parent component. For instance, the parent component could have local state management in place for the changing variable with React's setState() method. Once the variable changes in the local state, it can be passed down to your other component which is enhanced by the HOC. The HOC has access to the props and can execute the query with a changed variable. Hint: If you don't want to introduce another component in between to change the local state, recompose's withState HOC might be a great choice.

  • The second way, but maybe less elegant way, because it shifts your code from declarative to imperative, would be to use the withApollo() HOC which gives you access to the Apollo Client instance as prop. Having this instance in your React component, you can call client.query({ query: ..., variables: ... }) in one of your component's class methods (e.g. onChange handler) once your variable changes.

Since Apollo Client 2.1

Since Apollo Client 2.1 you can use the new Query (and Mutation) component. The HOC still exists though, but isn't advertised by the documentation anymore. However, the new Query (and Mutation) component is used within your component. It uses React's render props pattern.

import React from "react";
import { Query } from "react-apollo";
import gql from "graphql-tag";

const GET_TODOS = gql`
  {
    todos {
      id
      type
    }
  }
`;

const Todos = () => (
  <Query query={GET_TODOS}>
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;

      return data.todos.map(({ id, type }) => (
        <p key={id}>{type}</p>
      ));
    }}
  </Query>
);

By shifting this control flow inside of the component rather than having it only co-located with a HOC, you can pass the changing prop as variable to your Query (and Mutation) component.

import React from "react";
import { Mutation } from "react-apollo";
import gql from "graphql-tag";

const TOGGLE_TODO = gql`
  mutation ToggleTodo($id: Int!) {
    toggleTodo(id: $id) {
      id
      completed
    }
  }
`;

const Todo = ({ id, text }) => (
  <Mutation mutation={TOGGLE_TODO} variables={{ id }}>
    {(toggleTodo, { loading, error, data }) => (
      <div>
        <p onClick={toggleTodo}>
          {text}
        </p>
        {loading && <p>Loading...</p>}
        {error && <p>Error :( Please try again</p>}
      </div>
    )}
  </Mutation>
);

If you want to learn Apollo Client in React, checkout this extensive tutorial. Note: Code Snippets are taken from the Apollo Client 2.1 release blog post.

1
marktani On

Your ProfileWithData component does a good job of rendering the avatar of a certain user. It's not the responsiblity of this component to manage changes of the currently displayed avatar component.

One way is to add a new prop avatarId that gets passed down from the parent component. Then you would need to write this:

graphql(CurrentUserForLayout, {
  options: (ownProps) => ({
    variables: {
      id: ownProps.avatarId
    }
  })
})(Profile);

If you are familiar with react-router, another approach would be to use the route parameters to determine the avatar id. You would need to adjust the call of graphql like this:

graphql(CurrentUserForLayout, {
  options: (ownProps) => ({
    variables: {
      id: ownProps.params.avatarId
    }
  })
})(Profile);

To read more about the usage of query variables like this, you can check the React Track of Learn Apollo.

0
henk On

So far, I know two possible approaches to this.

The first one is already stated in the question. The variables are at the same time the props of the ProfileWithData. So you need to find a way to change the props and rerender the component. This is usually achieved with a parent component. So usually you do not change the variables inside of ProfileWithData. This component should be used only for display purposes. However, this does not say that it can't be done. You can pass the methods of your parent component to the child in order to manipulate the state of the parent. This will rerender both components and render the ProfileWithData with new props/variables.

The second approach is to query from the inside of the ProfileWithData component. You can then manipulate the variables from the inside of the component with the state. A method to do this can be found here

You can do it by passing a reference to Apollo Client using the withApollo higher-order-component, as documented here: http://dev.apollodata.com/react/higher-order-components.html#withApollo

Then, you can call client.query on the passed in object, like so:

 class MyComponent extends React.Component {
      runQuery() {
        this.props.client.query({
          query: gql`...`,
          variables: { ... },
        });
      }

      render() { ... }
    }

    withApollo(MyComponent);