Created
August 21, 2025 09:21
-
-
Save huozhi/10d8d06dc3f0feb922e946a9ba173b75 to your computer and use it in GitHub Desktop.
use-swr re-use item cache from list example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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