Revisiting Farcaster as a Comment System

This post was published 2023/11/29 & last updated 2023/11/29

buildfarcasternextjs

A year after implementing the Farcaster protocol for comments, it was time to refine and streamline the system. The original version, detailed in "Farcaster as a Comment System", worked but was due for an update.

Refining searchcaster.tsx

The core of this module, getCastThreads, effectively fetches and organizes comments into threads. The process involves:

  1. Fetching Parent Comments: Utilizing getTopLevelCasts based on a post's slug.
  2. Retrieving Child Comments: Employing getCastsByMerkleRoot for any replies.
Key Functions
import { RequestInfo } from "undici-types";

const BASE_URL = "https://searchcaster.xyz/api/search";

async function fetchCasts(uri: RequestInfo) {
  try {
    const response = await fetch(uri);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return (await response.json()).casts;
  } catch (error) {
    console.error("Fetch error:", error);
    throw error;
  }
}

export async function getCastThreads(slug: string) {
  const topLevelCasts = await getTopLevelCasts(slug);
  const castsWithChildren = await Promise.all(
    topLevelCasts.map(async (cast: any) => {
      const children =
        cast.meta.numReplyChildren > 0
          ? await getCastsByMerkleRoot(cast.merkleRoot)
          : [];
      return { ...cast, children };
    })
  );
  return castsWithChildren;
}

async function getTopLevelCasts(slug: string) {
  const uri = `${BASE_URL}?text=iammatthias.com/post/${slug}`;
  return fetchCasts(uri);
}

async function getCastsByMerkleRoot(merkleRoot: any) {
  const uri = `${BASE_URL}?merkleRoot=${merkleRoot}`;
  return fetchCasts(uri);
}

Enhancing Comments.tsx

This component renders the comments:

Functional Breakdown
  1. ParentComment: Filters out non-top-level comments.
  2. ChildComment: Implements recursion for nested comment threads.
  3. CommentBody: Displays comment content and metadata.
Rendering Logic

Comments fetches comments for a given slug, employing React's Suspense for smoother user experience. The comments are rendered in a list, processed by ParentComment or ChildComment based on their position in the thread.

function ParentComment({ comment }: { comment: any }) {
  if (comment.body.data.replyParentMerkleRoot !== null) {
    return null;
  }
  return <CommentBody comment={comment} />;
}

function ChildComment({ childComments }: { childComments: any }) {
  if (!Array.isArray(childComments)) {
    return null;
  }
  return childComments.map((comment: any) => (
    <CommentBody key={comment.merkleRoot} comment={comment} />
  ));
}

function CommentBody({ comment }: { comment: any }) {
  return (
    <li>
      <p>{comment.body.data.text}</p>
      {comment?.children > 0 && (
        <ul>
          <ChildComment childComments={comment.children} />
        </ul>
      )}
    </li>
  );
}

export default async function Comments({ slug }: { slug: string }) {
  const topLevelCasts = await getCastThreads(slug);
  if (topLevelCasts.length === 0) {
    return null;
  }
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <ul>
        {topLevelCasts.map((comment) => (
          <ParentComment key={comment.merkleRoot} comment={comment} />
        ))}
      </ul>
    </Suspense>
  );
}

It's a marked improvement. The code is cleaner, and renders faster. We've eliminated some redundancy, and set the groundwork for additional features down the line.

Outcome

The refactored system is noticeably cleaner and faster. It reduces redundancy and lays the foundation for future enhancements. This evolution not only improves current functionality but also prepares the system for additional features.

Back