Load More with React Hook and GraphQL Apollo Client in Typescript

May Chen
NEXL Engineering
Published in
2 min readFeb 18, 2021
demo

The problem

In our project we have a table of 9 million company records, and we have a GraphQL query to search in the 9 million records and return the best matching results. We first implemented it with pagination, so the query returns something like this:

prospectSearch(
term: String!
country: String
headCount: HeadCountRange
page: Int
perPage: Int
) {
entries {
...SearchResultEntry
}
pageInfo {
totalEntries
}
}

And the React component looks like this:

...return (
<div>
<Table>
{data.lists.entries.map(entry => <TableRow>
...
</TableRow>)}
</Table>
<Pagination
page={page}
setPage={setPage}
rowsPerPage={perPage}
setRowsPerPage={setPerPage}
count={data.lists.pageInfo.totalEntries}
/>
</div>
);
...

But the problem is that we need to get the total count of records for <Pagination /> component to know how many pages are there, and totalEntries really slow the query down as it has to go through the 9 millions record to count how many results matches the search.

The solution

So we wanted to implement “load more” functionality which doesn’t depend on totalEntries, we can keep querying next page until the query gives no more result.

In the wrapper component, we have the query and the load more logic:

export const SearchProspects: React.FC = ({}) => {
const [page, setPage] = useState<number>(0);
const [prospects, setProspects] = useState([]);
const [noMoreResult, setNoMoreResult] = useState(false);
const { loading, error } = useGetProspectSearchListQuery({
variables: {
page: page,
perPage: 25,
...searchParams
},
onCompleted: (data) => data.prospectSearch && (
data.prospectSearch.entries.length > 0
? setProspects(prospects.concat(data.result.entries))
: setNoMoreResult(true),

});
const handleLoadMore = () => setPage((prev) => prev + 1); return (
<SearchProspectsUI
prospects={prospects}
loading={loading}
error={error}
onLoadMore={handleLoadMore}
noMoreResult={noMoreResult}
/>
);
};

The key is in the query onCompleted function, if there’s more result coming back, we append the result to the previous result array, otherwise we set the flag noMoreResult to true so we can let the UI component know and hide the load more button and show user something like “there’s no more result”.

The UI component look like this:

...<TableBody>
{prospects.map((prospect, index) => (
<Row key={index} prospect={prospect} />
))} //show all the prospects

</TableBody>
{loading || error || prospects.length === 0 ? (
//...show loading/error/empty state
) : (
<Box marginTop={1} display="flex" justifyContent="center">
{noMoreResult ? ( // show there's no more prospects
<Typography variant="body2" color="textSecondary">
That's all the prospects we have.
</Typography>
) : ( // otherwise show load more button
<Button onClick={onLoadMore}>
Load More
</Button>
)}
</Box>
)}
...

That’s it, as simple as it is =)

--

--

May Chen
NEXL Engineering

A developer who occasionally has existential crisis and thinks if we are heading to the wrong direction, technology is just getting us there sooner.