Building a Project with SE-2 | Crowd Fund | Part Ten | Infinite Comments
If you are here for the first time, then you may want to start at the beginning of this series.
You are currently on the second article for Crowd Fund V4.
Here is a link to the previous article.
If you wish to go back through this series, you can view all of my blogs here.
Here are some links to the project files that are helpful to have open while going through this blog:
Before we begin, let’s take a peek at what these “Infinite Comments” will look like …
Obviously, by now … this is Part Ten — if you are not sure what we are doing, check out Part Nine.
Let’s dive in
Similar to the Posts in Part Nine, these Comments will begin as events that are emitted from the contract.
event Comment (
uint256 numericalId,
bytes postId,
bytes parentCommentId,
string commentText,
address commenter
);
function createComment(bytes memory _postId, bytes memory _parentCommentId, string memory _commentText) external {
emit Comment(
numberOfComments,
_postId,
_parentCommentId,
_commentText,
msg.sender
);
numberOfComments++;
}
Note that I am only using the numberOfComments
to keep all of these in order when we query them from our frontend.
The Queries
GraphQL does not allow for recursively-nested objects (which would be a truer form of “Infinite Comments”).
You could give it a Google, but I will save you some time:
- GraphQL specs discussion in Github
- GraphQL Recursive Query with Fragments
- Should GraphQL allow Recursive Fragments
We’re still going to make it work.
I tried this several different ways, asked around, and … There’s a bit of Prop Drilling going on in the frontend, but other than that — I am pleased with this. If I look back in a year and find this code embarrassing — I will let you know.
Let’s look at the components tree…
I sectioned off the portions of the tree that are re-used throughout 👇
Here is a look at where we are running queries 👇
So our page will get the PostId
from the URL, and then query two levels deep — Comments and their Sub-Comments (for this single Post).
I am getting around the Recursion problem with SubSubComments.tsx 👇
Basically, SubSubComments
is going to query for more comments (from a parent comment’s CommentId
) until there aren’t any comments left to display.
But, if you look at the schema.graphql, there aren’t any “sub-comments” Entities — just comments:
type Comment @entity {
id: Bytes!
numericalId: BigInt!
parentCommentId: Bytes!
commentText: String!
commenter: Bytes!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
socialPost: SocialPost
subcomments: [Comment!] @derivedFrom(field: "comment")
comment: Comment
likes: [CommentLike!] @derivedFrom(field: "comment")
likeCount: Int!
}
Looking at the Comment Entity above, note that parentCommentId
is not nullable (top-level comments will have a parentCommentId
value of ‘0x’); however, the Comment and the Post can be nullable. This means that these Comments can be used for a Post OR another Comment.
We have to look at the subgraph mapping to see how that works …
export function handleComment(event: CommentEvent): void {
let entity = new Comment(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.numericalId = event.params.numericalId;
entity.parentCommentId = event.params.parentCommentId;
entity.commentText = event.params.commentText;
entity.commenter = event.params.commenter;
entity.likeCount = 0;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
let postEntity = SocialPost.load(event.params.postId);
if (postEntity !== null) {
entity.socialPost = postEntity.id;
}
//for comments-of-comments
let commentEntity = Comment.load(event.params.parentCommentId);
if (commentEntity !== null) {
entity.comment = commentEntity.id;
}
entity.save();
}
☝️ This code is stating that — if there is a Post — we will set our Comment.socialPost
to THAT Post’s id; and likewise, if there is a (parent) Comment — we will set our Comment.comment
to THAT (parent) Comment’s id.
Note that Posts (and, Post “Likes”) were covered in the previous blog. You can “Like” Comments as well, and that process is very similar to the way Post “Likes” were handled.
Now — if we circle back to the start — we simply call createComment
from the frontend, and it will emit the Comment Event:
event Comment (
uint256 numericalId,
bytes postId,
bytes parentCommentId,
string commentText,
address commenter
);
function createComment(bytes memory _postId, bytes memory _parentCommentId, string memory _commentText) external {
emit Comment(
numberOfComments,
_postId,
_parentCommentId,
_commentText,
msg.sender
);
numberOfComments++;
}
Take a look at the CommentInteractions component to see the way we utilize Scaffold-Eth 2 to make this call:
const { writeAsync, isLoading } = useScaffoldContractWrite({
contractName: "CrowdFund",
functionName: "createComment",
args: [c.postId, c.commentId, thisCommentsText],
onBlockConfirmation: txnReceipt => {
console.log("📦 Transaction blockHash", txnReceipt.blockHash);
},
});
Here’s a complete look at the two queries being used 👇
//for viewing a Post's Comments
//used in /post/[postId].tsx -- Comments.tsx
export const GQL_SOCIAL_POST_COMMENTS_For_Display = () => {
return gql`
query ($socialPostId: String!, $userWalletAddress: String) {
comments(
orderBy: numericalId
orderDirection: desc
where: { parentCommentId: "0x", socialPost_: { id: $socialPostId } }
) {
id
commentText
commenter
likeCount
likes(where: { userWhoLiked: $userWalletAddress }) {
id
}
subcomments(orderBy: numericalId, orderDirection: desc) {
id
parentCommentId
commentText
commenter
likeCount
likes(where: { userWhoLiked: $userWalletAddress }) {
id
}
}
}
}
`;
};
//for viewing a Comment's Sub-Comments
//used in /post/[postId].tsx -- SubSubComments.tsx
export const GQL_SOCIAL_SUB_COMMENTS_For_Display = () => {
return gql`
query ($parentCommentId: String!, $userWalletAddress: String) {
comments(orderBy: numericalId, orderDirection: desc, where: { parentCommentId: $parentCommentId }) {
id
parentCommentId
commentText
commenter
likeCount
likes(where: { userWhoLiked: $userWalletAddress }) {
id
}
subcomments(orderBy: numericalId, orderDirection: desc) {
id
parentCommentId
commentText
commenter
likeCount
likes(where: { userWhoLiked: $userWalletAddress }) {
id
}
}
}
}
`;
};
That’s it for now, but updates are on the way.