Skip to content

Instantly share code, notes, and snippets.

@huozhi
Created August 21, 2025 09:21
Show Gist options
  • Save huozhi/10d8d06dc3f0feb922e946a9ba173b75 to your computer and use it in GitHub Desktop.
Save huozhi/10d8d06dc3f0feb922e946a9ba173b75 to your computer and use it in GitHub Desktop.
use-swr re-use item cache from list example
'use client'
import React from 'react'
import useSWR, { mutate } from 'swr'
interface Book {
id: string
title: string
author: string
publishedYear: number
}
const fetcher = async (url: string) => {
console.log(`๐Ÿ” Fetching: ${url}`)
const response = await fetch(url)
if (!response.ok) throw new Error('Failed to fetch')
return response.json()
}
const fetchBooks = async (): Promise<Book[]> => {
return fetcher('/api/books')
}
const fetchBook = async (id: string): Promise<Book> => {
return fetcher(`/api/book/${id}`)
}
const BookItem = ({ book, onClick, isExpanded }: {
book: Book
onClick: () => void
isExpanded: boolean
}) => {
// Or you can use useSWRImmutable
const { data: detailedBook, isLoading } = useSWR(
isExpanded ? `/book/${book.id}` : null,
isExpanded ? () => fetchBook(book.id) : null,
{
revalidateIfStale: false,
// revalidateOnFocus: false,
// revalidateOnReconnect: false
}
)
return (
<div className="border p-4 mb-2 cursor-pointer hover:bg-gray-50" onClick={onClick}>
<div className="flex justify-between items-center">
<h4 className="font-bold">{book.title}</h4>
<span className="text-sm text-gray-500">
{isExpanded ? 'โ–ผ' : 'โ–ถ'}
</span>
</div>
{isExpanded && (
<div className="mt-2 pl-4 border-l-2 border-blue-200">
{isLoading ? (
<p className="text-gray-500">Loading details...</p>
) : detailedBook ? (
<>
<p><strong>Author:</strong> {detailedBook.author}</p>
<p><strong>Published:</strong> {detailedBook.publishedYear}</p>
<p><strong>ID:</strong> {detailedBook.id}</p>
</>
) : null}
</div>
)}
</div>
)
}
export default function Page() {
const { data: books, isLoading } = useSWR('/books', fetchBooks)
const [expandedBookId, setExpandedBookId] = React.useState<string | null>(null)
React.useEffect(() => {
if (books) {
console.log('๐Ÿ“š Pre-populating book caches from books list')
books.forEach(book => {
mutate(`/book/${book.id}`, book, { revalidate: false })
})
}
}, [books])
const updateBookInCache = (updatedBook: Book) => {
console.log('๐Ÿ”„ Updating book cache without revalidation')
mutate(`/book/${updatedBook.id}`, updatedBook, { revalidate: false })
if (books) {
const updatedBooks = books.map(b =>
b.id === updatedBook.id ? updatedBook : b
)
mutate('/books', updatedBooks, { revalidate: false })
}
}
const handleUpdateFirstBook = () => {
if (!books || books.length === 0) return
const firstBook = books[0]
const updatedBook = { ...firstBook, title: `${firstBook.title} (Updated)` }
updateBookInCache(updatedBook)
}
const handleBookClick = (bookId: string) => {
setExpandedBookId(expandedBookId === bookId ? null : bookId)
}
if (isLoading) return <div>Loading books...</div>
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">SWR Cache Reuse Example</h1>
<div className="mb-6 p-4 bg-blue-50 rounded">
<h3 className="font-semibold mb-2">Instructions:</h3>
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>Books list loads first and pre-populates individual book caches</li>
<li>Click any book to expand details - should NOT trigger fetch (cache reused)</li>
<li>Check console for fetch logs to verify cache reuse</li>
<li>Use "Update First Book" to test cache mutation without revalidation</li>
</ol>
</div>
<button
onClick={handleUpdateFirstBook}
className="bg-blue-500 text-white px-4 py-2 rounded mb-4"
>
Update First Book Title
</button>
<div>
<h2 className="text-xl font-semibold mb-4">Books List (Click to expand):</h2>
{books?.map(book => (
<BookItem
key={book.id}
book={book}
onClick={() => handleBookClick(book.id)}
isExpanded={expandedBookId === book.id}
/>
))}
</div>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment