diff --git a/package-lock.json b/package-lock.json index 60b78ca..8ef650b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "0.0.0", "dependencies": { "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "react-virtuoso": "^4.18.3", + "react-window": "^2.2.7" }, "devDependencies": { "@eslint/js": "^9.39.4", @@ -1491,9 +1493,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -2253,6 +2255,26 @@ "react": "^19.2.4" } }, + "node_modules/react-virtuoso": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.3.tgz", + "integrity": "sha512-fLz/peHAx4Eu0DLHurFEEI7Y6n5CqEoxBh04rgJM9yMuOJah2a9zWg/MUOmZLcp7zuWYorXq5+5bf3IRgkNvWg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16 || >=17 || >= 18 || >= 19", + "react-dom": ">=16 || >=17 || >= 18 || >=19" + } + }, + "node_modules/react-window": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz", + "integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/package.json b/package.json index dc33ccf..b62b2ee 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ }, "dependencies": { "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "react-virtuoso": "^4.18.3", + "react-window": "^2.2.7" }, "devDependencies": { "@eslint/js": "^9.39.4", diff --git a/src/components/PrintingRow/PrintingRow.jsx b/src/components/PrintingRow/PrintingRow.jsx index 6d5fe68..0a4fa74 100644 --- a/src/components/PrintingRow/PrintingRow.jsx +++ b/src/components/PrintingRow/PrintingRow.jsx @@ -1,11 +1,13 @@ -import React, { useContext, useState, memo } from 'react'; +import React, { useContext, useState } from 'react'; import { CardContext } from '../../store/CardContext'; function PrintingRow({ card_id, printing }) { const { ownedAmounts, updateAmount } = useContext(CardContext); const [loading, setLoading] = useState(false); - const currentAmount = ownedAmounts[card_id]?.[printing.set_id]?.[printing.rarity_id] ?? printing.amount_owned ?? 0; + // Combined key for uniqueness + const key = `${printing.set_id}-${printing.rarity_id}`; + const currentAmount = ownedAmounts[card_id]?.[key] ?? printing.amount_owned ?? 0; const updateBackend = async (newAmount) => { const { set_id, rarity_id } = printing; @@ -16,22 +18,39 @@ function PrintingRow({ card_id, printing }) { const response = await fetch('http://localhost:3000/collection/amount', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ card_id, set_id, rarity_id, amount_owned: newAmount }) + body: JSON.stringify({ + card_id, + set_id, + rarity_id, + amount_owned: newAmount + }) }); if (response.ok) { - updateAmount(card_id, set_id, rarity_id, newAmount); + // Update context using the combined key + updateAmount(card_id, key, newAmount); } + } catch (err) { + // silently fail } finally { setLoading(false); } }; const increment = () => updateBackend(currentAmount + 1); - const decrement = () => { if (currentAmount > 0) updateBackend(currentAmount - 1); }; + const decrement = () => { + if (currentAmount > 0) updateBackend(currentAmount - 1); + }; return ( -
+
{printing.set_name} {printing.rarity_name}
@@ -42,4 +61,21 @@ function PrintingRow({ card_id, printing }) { ); } -export default memo(PrintingRow); \ No newline at end of file +// ✅ Memoize PrintingRow for performance +export default React.memo( + PrintingRow, + (prevProps, nextProps) => { + // Re-render only if card_id changes or printing reference changes + // or if the current amount changed + const prevKey = `${prevProps.printing.set_id}-${prevProps.printing.rarity_id}`; + const nextKey = `${nextProps.printing.set_id}-${nextProps.printing.rarity_id}`; + const prevAmount = prevProps.printing.amount_owned; + const nextAmount = nextProps.printing.amount_owned; + + return ( + prevProps.card_id === nextProps.card_id && + prevKey === nextKey && + prevAmount === nextAmount + ); + } +); \ No newline at end of file diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index d3eee4c..d2d8042 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useContext } from 'react'; +import { Virtuoso } from 'react-virtuoso'; import CardRow from '../components/CardRow/CardRow'; import SearchBar from '../components/SearchBar/SearchBar'; import { fetchCards } from '../services/api'; @@ -59,12 +60,16 @@ function HomePage() { return (
{/* Left panel: card list */} -
+

Card List

- {filteredCards.map(card => ( - - ))} + + {/* ✅ Virtualized list */} + } + />
{/* Right panel: card image */}