Building a Project with SE-2 | Crowd Fund | Part Ten | Infinite Comments

WebSculpt
5 min readMar 21, 2024

--

Image from Shubham Dhage on Unsplash

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 …

A view of the “Infinite Comments”

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:

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…

Here is a tree-view of the components

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).

Viewing a Post from a Fund Run

I am getting around the Recursion problem with SubSubComments.tsx 👇

The SubSubComments component is the parent of another SubSubComments component

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.

--

--

WebSculpt

Blockchain Development, coding on Ethereum. Condensed notes for learning to code in Solidity faster.