apollo-feature-requests icon indicating copy to clipboard operation
apollo-feature-requests copied to clipboard

Query consolidation

Open luin opened this issue 2 years ago • 1 comments

Something like Batch HTTP Link, but instead of batching multiple queries into a single HTTP request, we should be able to consolidate them into one larger query.

Example

const Book = ({ id }) => {
  return <div>
    <BookAuthor bookId={id}>
    <BookContent bookId={id>
  </div>
};

const BookAuthorQuery = gql(`
  query BookAuthor {
    book(id: $id) {
      id
      author {
        id
        avatar
      }
    }
  }
`);

const BookAuthor = () => {
  const { data } = useQuery(BookAuthorQuery);
  return ...
};

const BookContentQuery = gql(`
  query BookContent {
    book(id: $id) {
      id
      content
      author {
        id
        name
      }
    }
  }
`)

const BookContent = () => {
  const { data } = useQuery(BookAuthorQuery);
  return ...
};

With the feature enabled, Apollo will only send one single query:

query {
    book(id: $id) {
      id
      content
      author {
        id
        avatar
        name
      }
    }
}

Reasons

Consolidate queries would improve the backend performance by reducing database hits. This sometimes can be done via prop drilling. However, it's not always possible in a real application as the components may not directly related and could be rendered in a different part of the application.

luin avatar Feb 16 '24 15:02 luin

Hey @luin 👋

Thanks for the feature request!

To be totally transparent, in the future we will be moving away from recommending composition on useQuery hooks and instead will be recommending fragment composition and colocation for this kind of thing. I'd encourage you to take a look at that doc for more information. I'd encourage you to try and leverage this pattern first.

Using that approach, you can both avoid both network requests and ensure both components get the data they need by defining fragments for each of the child fragments and moving the useQuery call up to the parent:

const BookQuery = gql`
  query BookAuthor {
    book(id: $id) {
      id
      author {
        id
        ...BookAuthorFragment
      }
      ...BookContentFragment
    }
  }

  ${BookAuthor.fragments.author}
  ${BookAuthor.fragments.book}
`;

const Book = ({ id }) => {
  const { data } = useQuery(BookQuery, { variables: { id } });

  // loading state omitted for brevity

  return (
    <div>
      <BookAuthor {data.book.author}>
      <BookContent book={data.book}>
    </div>
  );
}



const BookAuthor = ({ author }) => {
  // do something with author
}

// NOTE: The `fragments` property is more of a convention than a requirement. 
// Feel free to use whatever convention you like most :)
BookAuthor.fragments = {
  author: gql`
    fragment BookAuthorFragment on Author {
      id
      avatar
      name
    }
  `
}

const BookContent = ({ book }) => {
  return // ...
};

BookContent.fragments = {
  book: gql`
    fragment BookContentFragment on book {
      id
      content
    }
  `
}

To be totally transparent, it is unlikely we will consider this feature in favor of the recommendation above. It's also VERY tricky to get right since we'd need to track renders across multiple components in order to make this work.

I'd highly encourage you to try this pattern! We'll have more official announcements/recommendations on the matter soon, so please be on the lookout for that.

jerelmiller avatar Mar 11 '24 18:03 jerelmiller