Shinmun Blog - A lightweight file-based blog engine

React Components Demo

This page demonstrates Shinmun’s powerful React integration, showcasing how to embed interactive React components directly in your Markdown pages using TypeScript.

Why React?

React is a JavaScript library for building user interfaces. Combined with TypeScript, it provides:

Shinmun automatically loads React from a CDN via import maps, so you can start building components immediately.


Hello World Component

Let’s start with a simple React component that renders a greeting. This demonstrates:

Source Code:

interface HelloProps {
  name: string;
}

function Hello({ name }: HelloProps) {
  return (
    <div style={{ padding: '1rem', background: 'linear-gradient(...)' }}>
      <h2>Hello, {name}! 👋</h2>
      <p>This is a React component embedded in Shinmun.</p>
    </div>
  );
}

Theme Switcher

A more advanced component demonstrating multiple hooks and CSS-in-JS patterns:

TypeScript Patterns Used:

// Union type for theme options
type Theme = 'light' | 'dark' | 'ocean' | 'forest';

// Record type for theme configurations
const themes: Record<Theme, ThemeConfig> = { ... };

// useEffect for side effects
useEffect(() => {
  setIsTransitioning(true);
  const timer = setTimeout(() => setIsTransitioning(false), 300);
  return () => clearTimeout(timer); // Cleanup!
}, [currentTheme]);

Animated Data Chart

Interactive bar chart showcasing data visualization with React:

Animation Pattern:

// Smooth CSS transitions
const barStyle: React.CSSProperties = {
  height: `${(point.value / maxValue) * 160}px`,
  transition: 'height 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
  transform: isAnimating ? 'scaleY(0.1)' : 'scaleY(1)',
  transformOrigin: 'bottom'
};

A photo gallery component perfect for blogs with image content:

Key Patterns:

// Category filtering with useState
const [category, setCategory] = useState<Category>('all');

const filteredPhotos = category === 'all' 
  ? photos 
  : photos.filter(p => p.category === category);

// Modal state management
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);

Reading Progress Indicator

A reading progress component commonly used in blog posts:

Scroll Tracking Pattern:

// useCallback for optimized scroll handler
const handleScroll = useCallback(() => {
  const { scrollTop, scrollHeight, clientHeight } = contentRef.current;
  const scrollProgress = (scrollTop / (scrollHeight - clientHeight)) * 100;
  setProgress(Math.min(100, Math.max(0, scrollProgress)));
}, []);

// useEffect for scroll event listener
useEffect(() => {
  content.addEventListener('scroll', handleScroll);
  return () => content.removeEventListener('scroll', handleScroll);
}, [handleScroll]);

How to Create Your Own Components

Step 1: Create a TSX File

Create your component in public/apps/:

// public/apps/my-widget.tsx
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';

function MyWidget() {
  const [value, setValue] = useState('');
  
  return (
    <div style={{ padding: '1rem', background: '#f5f5f5' }}>
      <input 
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Type something..."
      />
      <p>You typed: {value}</p>
    </div>
  );
}

// Mount to container
const container = document.getElementById('my-widget');
if (container) {
  const root = createRoot(container);
  root.render(<MyWidget />);
}

Step 2: Reference in Markdown

    @@typescript-file[my-widget](public/apps/my-widget.tsx)

Step 3: That’s It!

Shinmun handles:


React Hooks Cheat Sheet

useState - State Management

// Basic
const [count, setCount] = useState(0);

// With type
const [user, setUser] = useState<User | null>(null);

// Lazy initialization
const [data, setData] = useState(() => expensiveInit());

useEffect - Side Effects

// On mount/unmount
useEffect(() => {
  console.log('mounted');
  return () => console.log('unmounted');
}, []);

// On dependency change
useEffect(() => {
  fetchData(id);
}, [id]);

useMemo - Memoization

const computed = useMemo(() => {
  return expensiveCalculation(input);
}, [input]);

useCallback - Stable Callbacks

const handler = useCallback((e: Event) => {
  handleEvent(e, dependency);
}, [dependency]);

File Structure

public/
  apps/
    hello-react.tsx      # Simple greeting
    counter-react.tsx    # Counter with hooks
    todo-react.tsx       # Full CRUD todo list
    theme-switcher.tsx   # Theme switching demo
    animated-chart.tsx   # Data visualization
    photo-gallery.tsx    # Photo gallery with lightbox
    reading-progress.tsx # Blog reading progress indicator
    form-validation.tsx  # TypeScript form validation
    search-filter.tsx    # Real-time search

Prerequisites

  1. Install esbuild: npm install esbuild
  2. Create your TSX files in public/apps/
  3. Reference them in your markdown with @@typescript-file

React and ReactDOM are automatically loaded from a CDN—no additional dependencies needed!


Categories

Archive