Reputation: 1272
I have a situation where I need to fetch e.g. all articles posted by a user when a component is mounted. To get a user's articles I am using the following query:
const GET_USER_ARTICLES = gql`
query getUserArticles($id: ID, $numArticles: Int!, $cursor: String) {
user(id: $id) {
id
articles(first: $numArticles, after: $cursor, orderBy: "-created", state: "enabled") @connection(key: "userArticles") {
edges {
node {
name
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
`;
If there is a next page I want to keep fetching more articles until I have ALL of them. Up until now I haven't had the need to do anything like this (normally I have a button the user can click "Load more" to fetch more articles for example, but now need to fetch everything without a user interacting with anything), so I'm not sure what the best way to go about this is.
An example of the query in React:
const PAGE_SIZE = 10;
const { data, loading, fetchMore } = useQuery<UserArticlesData, UserArticlesVariables>(
GET_USER_ARTICLES,
{ variables: { id: userId, numArticles: PAGE_SIZE, cursor: null } },
);
I am a little lost how I can use the fetchMore
to keep fetching until there aren't any more pages left, while also showing a loading state to the user. I'm also not sure this is the best way to go about this in the first place, so any suggestions are more than welcome!
Upvotes: 2
Views: 3714
Reputation: 84687
If the API does not limit the page size, you could just provide an arbitrarily large number as the page size to get the remaining results. Assuming the page size can only be so big, though, you can do something like this:
const { data, loading, fetchMore } = useQuery(GET_USER_ARTICLES, {
variables: { id: userId, numArticles: PAGE_SIZE, cursor: null },
notifyOnNetworkStatusChange: true,
})
const fetchRest = async () => {
const { user: { articles: { pageInfo } } } = data
const updateQuery = (prev, { fetchMoreResult }) => {
// Merge the fetchMoreResult and return the combined result
}
let hasNextPage = pageInfo.hasNextPage
let cursor = pageInfo. endCursor
while (hasNextPage) {
const { data } = await fetchMore({
variables: { id: userId, numArticles: PAGE_SIZE, cursor },
updateQuery,
})
const { user: { articles: { pageInfo } } } = data
hasNextPage = pageInfo.hasNextPage
cursor = pageInfo. endCursor
}
}
By setting notifyOnNetworkStatusChange
to true
, loading
will be updated whenever fetchMore
is doing any fetching. Then we just loop until hasNextPage
is called. fetchMore
returns a Promise that resolves to the query result, so we can use the query response outside the updateQuery
function.
Note that this is a rough example -- you might actually want to keep track of loading state yourself, for example. If your API has rate limiting, your logic should account for that as well. However hopefully this gives you a good starting point.
Edit:
If you need to get all the articles initially, I wouldn't use useQuery
and fetchMore
at all. The easiest workaround would be to manage the data and loading state yourself and utilize client.query
instead.
const client = useApolloClient()
const [data, setData] = useState()
const [loading, setLoading] = useState(true)
const fetchAll = async () => {
let hasNextPage = true
let cursor = null
let allResults = null
while (hasNextPage) {
const { data } = await client.query(GET_USER_ARTICLES, {
variables: { id: userId, numArticles: PAGE_SIZE, cursor },
})
// merge data with allResults
hasNextPage = pageInfo.hasNextPage
cursor = pageInfo. endCursor
}
setLoading(false)
setData(allResults)
}
useEffect(() => {
fetchAll()
}, [])
Upvotes: 4