React
Vite + React with the Content instance plugin and content HMR.
Setting up your own app? Follow the Vite integration guide step by step.
App.tsx
import { useEffect, useState, useSyncExternalStore } from 'react'
import { MarkdownDocument } from '@comark/react'
import { content, getContentVersion, subscribeToContent } from './content'
import { ContentFile } from 'comark-content'
type Item = Awaited<ReturnType<typeof content.list>>[number]
function usePathname() {
const [path, setPath] = useState(window.location.pathname)
useEffect(() => {
const syncPath = () => setPath(window.location.pathname)
window.addEventListener('popstate', syncPath)
return () => window.removeEventListener('popstate', syncPath)
}, [])
return [path, setPath] as const
}
export default function App() {
const [path, setPath] = usePathname()
const contentVersion = useSyncExternalStore(subscribeToContent, getContentVersion, getContentVersion)
const [items, setItems] = useState<Item[]>([])
const [page, setPage] = useState<ContentFile | null>(null)
const [pending, setPending] = useState(true)
const [error, setError] = useState<string | null>(null)
function navigate(event: React.MouseEvent<HTMLAnchorElement>, to: string) {
event.preventDefault()
if (to === path) return
history.pushState({}, '', to)
setPath(to)
}
useEffect(() => {
let cancelled = false
async function load() {
setPending(true)
setError(null)
try {
const [nextPage, nextItems] = await Promise.all([content.get(path), content.list()])
if (cancelled) return
setPage(nextPage ? nextPage : null)
setItems(nextItems)
if (!nextPage) setError('Not found')
} catch (err) {
if (cancelled) return
setError((err as Error).message)
setPage(null)
} finally {
if (!cancelled) setPending(false)
}
}
void load()
return () => {
cancelled = true
}
}, [path, contentVersion])
return (
<div className="layout">
<aside>
<h2>Pages</h2>
<nav>
{items.map((item) => (
<a
aria-current={path === item.path ? 'page' : undefined}
className={path === item.path ? 'active' : undefined}
href={item.path}
key={item.path}
onClick={(event) => navigate(event, item.path)}
>
{item.data?.title ?? item.meta.stem}
</a>
))}
</nav>
</aside>
<main>
{pending ? <p>Loading...</p> : page ? <MarkdownDocument value={page} /> : <p>{error ?? 'Not found'}</p>}
</main>
</div>
)
}