in issue-tracker/src/IssueDetailComments.js [14:118]
export default function IssueDetailComments(props) {
// Given a reference to an issue in props.issue, defines *what*
// data the component needs about that repository. In this case we fetch
// the list of comments starting at a given cursor (initially null to start
// at the beginning of the issues list). See the usePaginationFragment()
// docs: https://relay.dev/docs/en/experimental/api-reference#usepaginationfragment
// for more details about how to use this hook to paginate over lists.
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment(
graphql`
fragment IssueDetailComments_issue on Issue
@argumentDefinitions(
cursor: { type: "String" }
count: { type: "Int", defaultValue: 10 }
)
@refetchable(queryName: "IssueDetailCommentsQuery") {
comments(after: $cursor, first: $count)
@connection(key: "IssueDetailComments_comments") {
edges {
__id
node {
id
author {
login
avatarUrl
}
body
}
}
}
}
`,
props.issue,
);
// Individual comments may suspend while any images are loading (for the
// author avatar or content within the comment body). Using `useTransition()`
// allows us to continue showing existing comments while the next page of
// results is still loading in the background.
const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
// Callback to paginate the issues list
const loadMore = useCallback(() => {
// Don't fetch again if we're already loading the next page
if (isLoadingNext) {
return;
}
startTransition(() => {
loadNext(10);
});
}, [isLoadingNext, loadNext, startTransition]);
const comments = data.comments.edges;
if (comments == null || comments.length === 0) {
return <div className="issue-no-comments">No comments</div>;
}
// Per above, individual comments may suspend while images load. Using <SuspenseList>
// allows us to render comments as they are ready, while avoiding showing them out of
// order, as could happen if images for a later comment resolved before images for
// an earlier comment.
return (
<>
<SuspenseList revealOrder="forwards">
{comments.map(edge => {
if (edge == null || edge.node == null) {
return null;
}
const comment = edge.node;
return (
// Wrap each comment in a separate suspense fallback to allow them to commit
// individually; SuspenseList ensures they'll reveal in-order.
<Suspense fallback={null} key={edge.__id}>
<div className="issue-comment">
<SuspenseImage
className="issue-comment-author-image"
title={`${comment.author.login}'s avatar`}
src={comment.author.avatarUrl}
/>
<div className="issue-comment-author-name">
{comment.author.login}
</div>
<div className="issue-comment-body">
<ReactMarkdown
source={comment.body}
renderers={{ image: SuspenseImage }}
/>
</div>
</div>
</Suspense>
);
})}
</SuspenseList>
{hasNext ? (
<button
name="load more comments"
type="button"
className="issue-comments-load-more"
onClick={loadMore}
disabled={isPending || isLoadingNext}
>
{isPending || isLoadingNext ? 'Loading...' : 'Load More'}
</button>
) : null}
</>
);
}