Load More with React Hook and GraphQL Apollo Client in Typescript
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 =)