5

I have a graphql endpoint: QueryDataForOptions

I want to call it multiple times based on the component's options prop. There may be any number of options.

I would like to do something like this pseudocode but the rules of hooks do not allow it:

const MyComponent = (options) => {
  options.forEach(option => {
    const {data, loading, error} = useQuery(QueryDataForOptions, {variables: {option}})
  })

}

I also considered this pseudocode but I am not sure how to handle data being overwritten:

const MyComponent = (options) => {
  const [getOptions, {data, loading, error}] = useLazyQuery(QueryDataForOptions)
  options.forEach(option => {
    getOptions({variables: {option}})
  })
}

I know the right solution is to change the graphql endpoint but I want to do this change on the client side.

How do I make multiple calls of the same query?

2
  • Can you show us what QueryDataForOptions looks like? Commented Jan 27, 2020 at 20:57
  • It's made up but would look something like: Query.dataForOptions( option: Int! )DataForOptions! Commented Jan 27, 2020 at 21:15

2 Answers 2

5

Apollo does not expose a hook to query multiple operations simultaneously. However, GraphQL supports querying multiple root fields per operation. This fact, combined with the ability to alias fields, means you can get all your required data in a single request.

You should normally stay away from string interpolation when composing queries, but in this case we'll need to use it.

const MyComponent = (options) => {
  const query = gql`
  query SomeNameForYourOperation (
    ${options.map((option, index) => `$opt${index}: SomeInputType!`).join('\n')}
  ){
    ${options.map((option, index) => `
      alias${index}: someRootField(someArgument: $opt${index}) {
        ...SomeTypeFragment
      }
    `).join('')}
  }

  fragment SomeTypeFragment on SomeType {
    someField
    someOtherField
  }
  `

  const variables = options.reduce((memo, option, index) => {
    return { ...memo, [`opt${index}`]: option }
  }, {})

  const { data, loading, error } = useQuery(query, { variables })

  if (data) {
    // data.alias0
    // data.alias1
    // etc.
    // You can also iterate through options again here and access the
    // appropriate alias by the index
  }
}

This looks a bit complicated but it's actually pretty straightforward. We generate the variable definitions by iterating through the options (we end up with variables like $opt0, $opt1, etc.). We then iterate through the options again to generate the selection set for the root. For each option, we add the same field to our query, but we alias it (as alias0, alias1, etc.) and then pass a different variable to the argument for the field. Lastly, we utilize a fragment to keep the size of the resulting query manageable.

We also have to pass a variables object to useQuery that matches the variable definitions we generated, so we reduce the options array into an object with appropriately named properties.

Of course, you'll need to change the above example to include the types, fields and arguments specific to your query. The end result is that you have a single set of data, loading and error variables. Once the query loads, you can access each result under the appropriate alias (alias0, etc.).

Sign up to request clarification or add additional context in comments.

2 Comments

Wish Apollo provided a way to make this easier. Anyways, why even use variables since you're already building the query by yourself. Isn't it gonna be simpler if you put the variables in the query itself, also probably faster for the server.
We use variables so that it appears as readable objects in the Payloads tab in devtools
1

Instead of making a query for each option, you should query for all of them at once. So your query will take an array of what I assume will be ids for each option, and return back an array, one for each option. If you have 50 options, one query will be strikingly faster and better than trying to fire off one query for each option.

Another, less preferable option is to create a component for each option which fires off its own query. So something like

const MyComponent = (options) => (
  options.forEach(option => <OptionComponent option={option} />)
)

const OptionComponent = ({ option }) => {
  const [getOptions, {data, loading, error}] = useLazyQuery(QueryDataForOptions);

  if (loading) return 'Loading...';

  return (
    // whatever you want for each option
  );
}

1 Comment

That requires changes to the backend. I do not have control over that, unfortunately, so I need to solve this on the frontend.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.