Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to call multiple queries with useQuery? #120

Closed
midoru0121 opened this issue Mar 28, 2019 · 23 comments
Closed

Is it possible to call multiple queries with useQuery? #120

midoru0121 opened this issue Mar 28, 2019 · 23 comments

Comments

@midoru0121
Copy link

Hi,
In advance thank you great library.

In our product, there is the a page which needs data from 2 or 3 GraphQL queries and we want to call multiple queries with one useQuery().

export const Curriculums = ({ children }) => {

  const { data, error } = useQuery(QUERY_CURRICULUMS)

  /* We want to call useQuery as below 
  const { data, error } = useQuery([
     QUERY_CURRICULUMS, 
     QUERY_COURSES, 
     QUERY_LESSONS
     ]
  ) 
  */

  if (error) {
    throw error
  }

  return children(data)
}

Is it possible to do the same things as compose() method defined in react-apollo?

https://www.apollographql.com/docs/react/react-apollo-migration#compose-to-render-composition

Or should we call useQuery with each queries?

Thanks.

@FezVrasta
Copy link

FezVrasta commented Mar 28, 2019

compose just calls multiple HOCs together, but it doesn't anything else, in fact you have to map the result of the queries manually to avoid them from being overridden by the next query.

What you want to do is compose multiple useQuery into your own custom hook:

const queryMultiple = () => {
  const res1 = useQuery(...);
  const res2 = useQuery(...);
  return [res1, res2];
}

@midoru0121
Copy link
Author

@FezVrasta

Thank you for your quick response. I understand and we'll call useQuery as described :)
I'll close the issue.

@dannycochran
Copy link

@FezVrasta I think I am noticing a similar issue, not sure if this is expected behavior. The top level of my app is wrapped in an <ApolloHooksProvider />.

I have two components in the tree that each call useQuery with distinct queries against completely different parts of the schema. The component which renders last seems to cancel the in flight /graphql request created by the first component's useQuery call. Is it expected that only one network request can be in flight at any given time from useQuery? If so, that feels like an unnecessary restriction.

@MarioKrstevski
Copy link

I have a simple solution that worked for me.

const { data: dataR, error: errorR, loading: landingR } = useQuery(GET_RESTAURANTS);
const { data, error, loading } = useQuery(GET_DAILY_MENU);

I am simply renaming the fields useQuery is giving me in the first call, so that they can live in the same scope, otherwise the second query will overwrite the first.

@TSMMark
Copy link

TSMMark commented Oct 9, 2019

Because react does not allow you to have a variable number of hooks, I still think there is a usecase for ONE hook that performs multiple queries.

In our situation, we are paginating a table and preloading a variable number of next pages.

For example, if user visits page 1, we preload page 2 and 3 after the

First ideas of API would be something like this:

const { data, loading, error } = useQuery(LIST, { variables: { limit: 10, offset: 0 } })
useManyQueries([
  [LIST, { skip: !data, variables: { limit: 10, offset: 1 } }],
  [LIST, { skip: !data, variables: { limit: 10, offset: 2 } }],
])

In the above example I've hardcoded it which is achievable with a static number of hooks, however if numPreloadedPages becomes dynamic it's not possible as per my understanding.

Example:

const currentPage = 0
const limit = 10
const numPreloadedPages = someDynamicValue()
// Let's say numPreloadedPages => 4
const { data, loading, error } = useQuery(LIST, { variables: { limit, offset: currentPage * limit } })

const preloadPages = Array.from(new Array(numPreloadedPages)).map((_, i) => i + currentPage + 1)
// => [1, 2, 3, 4]

useManyQueries(preloadPages.map((page) => [
  LIST, { skip: !data, variables: { limit, offset: page * limit } }
]))

Should I open a new issue for this?

@FezVrasta
Copy link

Why can't you use the existing "skip" option to conditionally run the queries?

@TSMMark
Copy link

TSMMark commented Oct 10, 2019

I don't see how that's related and if you read the code I posted I am using the skip option

@FezVrasta
Copy link

FezVrasta commented Oct 10, 2019

Because react does not allow you to have a variable number of hooks, I still think there is a usecase for ONE hook that performs multiple queries.

It's not needed, you can use skip to get what you want. That's my point.

Also, to be fair, your example is not that great, why can't you run a single query to get all your pages? You should just change the limit accordingly 🤷‍♂

@TSMMark
Copy link

TSMMark commented Oct 10, 2019

Sorry maybe I'm not explaining the problem well enough.

you can use skip to get what you want

You're still talking about a static number of useQuery. Sure. Yeah you could write 100 useQuery into a component and skip them dynamically. Am I missing something or are you really saying that is an ideal API?

run a single query to get all your pages

This only works if you have 100% cache redirect coverage, or I think possibly using returnPartialData?, which is not always possible or ideal. To my understanding, the way apollo resolves queries from the cache is by looking up that query from the cache given the exact same operationName + variables.

So if you have previously queried { offset: 0, limit: 100 }, and now are querying the same operation with variables { offset: 0, limit: 10 }, the cache will be missed and the network will be hit.

I'm fairly sure about that but please correct me if that's not the case.

P.S. if you want to learn how to use the expression "to be fair" correctly this is a good start https://www.collinsdictionary.com/us/dictionary/english/to-be-fair

@asotog
Copy link

asotog commented Dec 31, 2019

mm had same question i have 2 use queries in the same component fetching with different queries, but i see 2 http requests, is it possible to only make 1 request ? is there any configuration i'm missing ?

@ivan-kleshnin
Copy link

ivan-kleshnin commented Feb 10, 2020

@asotog something like this:

let DataQuery = gql`
  query BlogData {
    me {
      id
      email
    }
    posts {
      id
      title
    }  
  }`

function Page() {
  let router = useRouter()

  let {data, error, loading} = useQuery(DataQuery)
  ...
  // use data.me
  // use data.posts
}

?!

@TSMMark
Copy link

TSMMark commented Feb 10, 2020

@asotog you want to use the apollo link batch http https://www.apollographql.com/docs/link/links/batch-http/

@asotog
Copy link

asotog commented Feb 10, 2020

@asotog something like this:

let DataQuery = gql`
  query BlogData {
    me {
      id
      email
    }
    posts {
      id
      title
    }  
  }`

function Page() {
  let router = useRouter()

  let {data, error, loading} = useQuery(DataQuery)
  ...
  // use data.me
  // use data.posts
}

?!

@ivan-kleshnin @TSMMark sorry i'm new to apollo and graphql in general I have something like this:

const { data: carouselData } = useFetchCarousel();
const { data: featureSectionData } = useFetchFeaturedSection();

each of these hooks using useQuery with different queries each, but i see in the network log, 2 requests going to the graphql separate, so my question was if there is any way apollo handles the 2 request at the same time, or i need to put the 2 gql queries in same useQuery, what approach you would take in my case ? thanks in advance

@ivan-kleshnin
Copy link

ivan-kleshnin commented Feb 10, 2020

Two hooks will result in two HTTP requests because by using two hooks you're basically saying: "I want to apply different policies to two queries". Those policies can be, for example:

  • different query options
  • different loading / error handling
  • etc

My code above (single useQuery, multiple queried objects) will result in a single HTTP request.
Note that quering two objects at the same time will affect caching behavior.
But most of the times after:

query {
  foo
  bar
}

the consequent

query {
  foo
  baz
}

will reuse the cache for foo and won't query for it. So the "default" approach should probably be the one with a single useQuery and multiple query objects, not the one with multiple useQuery. I'd use the second only when it's really necessary.

@asotog
Copy link

asotog commented Feb 10, 2020

will try that thanks so much

@TSMMark
Copy link

TSMMark commented Feb 10, 2020

@asotog I still think the batch-http link might help you more than you think. It introduces http request batching automatically globally for queries that run "at the same time" within a certain timeout window. Using it, you could keep your multiple useQuery separate as they are, and let the apollo link take care of batching them when it makes sense. I'll also note that you can have individual queries opt out of batching, if needed.

However if you insist not using batch-http apollo link, you should follow @ivan-kleshnin's advice. But I will add to it: you might want to consider breaking your one query (comprised of the two smaller queries) into logical query Fragments (see links below). This is similar to how Relay works — Each component may "export" a query Fragment, then a parent component that actually makes the query is responsible for merging the query fragments into one complete query, and to make the actual query to the server.

This may help you achieve separation of data concerns, where your component still has defined colocated data dependencies, but you are merging the queries at a higher level to optimize HTTP traffic.

https://graphql.org/learn/queries/#fragments
https://www.apollographql.com/docs/react/v3.0-beta/data/fragments/

@difex01
Copy link

difex01 commented Mar 24, 2020

Something that worked for me was using another component :

User_Data.js

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import QUERY from '../../../Queries';
import Car_Info from './Car_Info';

const GET_USER_QUERY = QUERY.User.GET_USER_QUERY;

const User_Data = (props) => {
    
    const userId = props.match.params.id;

    const { loading, error, data } = useQuery(GET_USER_QUERY, {
        variables: {
            id: userId
        }
    });

    if(loading) return `Loading...`;
    if(error) return `Error: ${error.message}`;

    return (
        <Car_Info type={data.getUser.car_type}/>
    );
};

export default User_Data;

Car_Info.js

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import QUERY from '../../../Queries';

const GET_CAR_QUERY = QUERY.Car.GET_CAR_QUERY;

const Car_Info = ({type}) => {
    
    const userId = props.match.params.id;

    const { loading, error, data } = useQuery(GET_CAR_QUERY, {
        variables: {
            type
        }
    });
    
    if(loading) return `Loading...`;
    if(error) return `Error: ${error.message}`;

    return (
        <div> Content, etc </div>

    );
};

export default Car_Info;

Hope this help

@githubjosh
Copy link

githubjosh commented Apr 6, 2020

If it's helpful to anyone, here's a great resource with several approaches How to run multiple queries at once using GraphQL and Apollo Client

Here's one approach (from the article above):

image

Took me a lot of searching to find this.

@ryderwishart
Copy link

I have a simple solution that worked for me.

const { data: dataR, error: errorR, loading: landingR } = useQuery(GET_RESTAURANTS);
const { data, error, loading } = useQuery(GET_DAILY_MENU);

I am simply renaming the fields useQuery is giving me in the first call, so that they can live in the same scope, otherwise the second query will overwrite the first.

Another simple solution I found useful is to wait on destructuring the results until you can control the name you give each value.

const restaurantQueryResult = useQuery(GET_RESTAURANTS);
const menuQueryResult = useQuery(GET_DAILY_MENU);
const menuData = menuQueryResult.data
const restaurantData = restaurantQueryResult.data
...

@ifndefdeadmau5
Copy link

ifndefdeadmau5 commented Oct 26, 2020

@githubjosh I would disagree with assessment in that article which

While this works great with this example, it has two drawbacks:

  1. Since we’re re-using the same variables in both components, if the two queries don’t accept the same variables (or their type definitions), we can’t have multiple definitions for them. For example, one if the two queries is marking a variable as required and the second query doesn’t mark it as required, an error will be thrown in this case as the definitions are not matching.

This just doesn't make sense because we definitely can declare variables separately for each query that being combined

  1. It becomes messy easily when it grows and difficult to pass the data around

Question, which one sounds better, getting same data with 1 single query OR multiple query with multiple round trip without getting any benefits?
and I don't understand how can it be messier than have multiple useQuery hooks that'd end up use renaming like

const { data: queryAData, loading: queryALoading, error:queryAError } = useQuery(...)
const { data: queryBData, loading: queryBLoading, error:queryBError } = useQuery(...)

@Bankalstep
Copy link

Hello,

Trying to do as follow :

 const {data: dataReviews, loading: loadingReviews, error: errorReviews} = useQuery(GetReviews, {
});
const {data: dataRating, loading: loadingRating, error: errorRating} = useQuery(GetAverage, {
});

doesn't work for me. Most of the time on of the two requests just fails. I don't know how is that possible since that's a solution that many people have proved it valid ? how come that doesn't work for me ?

This is the whole code :

const Reviews: FunctionComponent = () => {
    const {data: dataReviews, loading: loadingReviews, error: errorReviews} = useQuery(GetReviews, {
        ssr: false,
        variables: {
            offset: 0,
            limit: 3
        }
    });

    const {data: dataRating, loading: loadingRating, error: errorRating} = useQuery(GetAverage, {
        ssr: false
    });
    
    if (loadingRating && loadingReviews) {
        return <div className={`${styles.loader}`}/>;
    }

    const reviews = !loadingReviews && !errorReviews && dataReviews ? dataReviews.reviews[0].reviews : null;
    const rating = !loadingRating && !errorRating && dataRating ? dataRating.average[0] : null;
    
    return (
        <div className={`${styles.netreviews_review_rate_and_stars}`}>
            <div className={`${styles.reviews_list}`}>
                <ReviewsSideInfo rating={rating} stats={reviews.stats} filter={reviews} recommandation={reviews}/>
                <ReviewsContainer reviews={reviews}/>
            </div>
        </div>
    );
}
export default Reviews;

At the end I get my variable reviews equals null and then the rendering fails. Any help would be greatly appreciated. Thanks.

@irgherasim
Copy link

irgherasim commented Jun 17, 2021

Hello everyone,
I have an use case for which I can't find any useful resources.
I have to run a query that returns a list of values.
For each value in the list I have to call a query to get extra details.
This means I have to run a variable number of useQuery, without knowing the number of times.

Is this achievable with usequery or should the change happen on server side to be able to send the list of inputs as an array and get the agregated results?

Thanks in advance!

I need something like this :
`const {data, loading} =useQuery(FIRST_QUERY) ;

const listOfValues = data?.values;

listOfValues. forEach(val => {
const {data: newData, loading : newLoading} =
useQuery(SECOND_QUERY, {
skip:! listaOfValues,
variables :{
sentValue: val
} } }) ;`

@seaweasel
Copy link

seaweasel commented Jan 7, 2023

For the sanity of future generations, give each useQuery call a "queryKey" config variable or you may end up with overlapping queries.

{ data: q1} = useQuery({queryKey:'q1', queryFn: () => /** resource 1 **/})
{ data: q2} = useQuery({queryKey:'q2', queryFn: () => /** resource 2 **/})

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

No branches or pull requests