Skip to main content

Blog Components

Components for displaying and managing blog content, including listing, filtering, and individual post views.

BlogList Component

The main blog listing page with search and filtering capabilities.

Import

import BlogList from './app/BlogList';

Usage

function App() {
  return <BlogList />;
}

State Management

isMobileMenuOpen
boolean
Controls mobile navigation menu visibility
searchQuery
string
Current search query for filtering posts
selectedCategory
string
default:"'All'"
Currently selected category filter
selectedTag
string
default:"'All'"
Currently selected tag filter

Filtering Logic

Posts are filtered using memoized computation:
const filteredPosts = useMemo(() => {
  let posts = allPosts;
  
  // Apply search
  if (searchQuery) {
    posts = searchPosts(posts, searchQuery);
  }
  
  // Apply category filter
  if (selectedCategory !== 'All') {
    posts = filterByCategory(posts, selectedCategory);
  }
  
  // Apply tag filter
  if (selectedTag !== 'All') {
    posts = filterByTag(posts, selectedTag);
  }
  
  return posts;
}, [allPosts, searchQuery, selectedCategory, selectedTag]);

Features

Full-text search across titles, excerpts, categories, and tags
  • Glass morphism design (backdrop-blur)
  • Focus states with orange accent
  • Search icon from lucide-react
Filter Dropdowns
feature
Category and tag filtering using FilterDropdown component
  • Dynamic options generated from available posts
  • Mobile-responsive layout (flex-col on mobile, flex-row on desktop)
Active Filters Display
feature
Visual badges showing currently active filters with clear all option
Separate grid for featured blog posts (md:grid-cols-2)
Regular Posts Grid
feature
Standard grid layout (md:grid-cols-2, lg:grid-cols-3)

Source Location

src/app/BlogList.tsx:11

BlogPost Component

Individual blog post page with full content, metadata, and AI summarization.

Import

import BlogPost from './app/BlogPost';

Usage

import { useParams } from 'react-router-dom';

function BlogPostPage() {
  const { slug } = useParams();
  return <BlogPost />;
}

State Management

isMobileMenuOpen
boolean
Mobile navigation toggle state
copied
boolean
Tracks if share link has been copied to clipboard
isChatOpen
boolean
Controls ChatBox visibility for AI summarization
blogContextForChat
BlogContext | undefined
Blog context passed to ChatBox for AI summarization

View Tracking

Uses Convex for real-time view counting:
const incrementView = useMutation(api.views.incrementView);
const viewCount = useQuery(api.views.getViewCount, { slug: postSlug ?? '' });

useEffect(() => {
  if (postSlug && hasTrackedRef.current !== postSlug) {
    const sessionKey = `blog_viewed_${postSlug}`;
    if (!sessionStorage.getItem(sessionKey)) {
      incrementView({ slug: postSlug });
      sessionStorage.setItem(sessionKey, '1');
    }
    hasTrackedRef.current = postSlug;
  }
}, [incrementView, postSlug]);

Actions

handleShare
() => void
Shares the blog post using Web Share API or copies URL to clipboard
if (navigator.share) {
  navigator.share({
    title: post?.title,
    text: post?.excerpt,
    url: window.location.href,
  });
} else {
  navigator.clipboard.writeText(window.location.href);
  setCopied(true);
}
handleSummarize
() => void
Opens ChatBox with blog context for AI summarization
setBlogContextForChat({
  title: post.title,
  content: post.content,
  excerpt: post.excerpt,
});
setIsChatOpen(true);

Metadata Display

Category Badge
element
Gradient badge with category name
Publication Date
element
Formatted date with Calendar icon
Reading Time
element
Estimated reading time with Clock icon
View Count
element
Real-time view count with Eye icon
Tags
element
Clickable tag badges that link to filtered blog list

Markdown Rendering

Custom ReactMarkdown components for styled content:
<ReactMarkdown
  components={{
    h1: ({ node, ...props }) => (
      <h1 className="text-4xl font-semibold mt-8 mb-4" {...props} />
    ),
    code: ({ node, ...props }: any) => {
      const isInline = !props.className?.includes('language-');
      if (isInline) {
        return <code className="bg-gray-800 px-2 py-1 rounded" {...props} />;
      }
      return <code className="block bg-gray-900 p-4 rounded-lg" {...props} />;
    },
    // ... more custom components
  }}
>
  {post.content}
</ReactMarkdown>

Source Location

src/app/BlogPost.tsx:15

Data Flow

Both components rely on blog utility functions:
  • getAllBlogPosts() - Fetches all blog metadata
  • getBlogPost(slug) - Fetches individual post with content
  • searchPosts() - Filters posts by search query
  • filterByCategory() - Filters by category
  • filterByTag() - Filters by tag
  • getAllCategories() - Gets unique categories
  • getAllTags() - Gets unique tags
  • formatDate() - Formats date strings

Dependencies

  • react-router-dom - URL routing and navigation
  • react-markdown - Markdown content rendering
  • convex/react - Real-time database hooks
  • lucide-react - UI icons
  • Custom blog utilities and types

Build docs developers (and LLMs) love