-
Notifications
You must be signed in to change notification settings - Fork 0
test: Rendering Performance Violations #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| import React from 'react'; | ||
| import { useSelector } from 'react-redux'; | ||
| import { getNfts, getAccounts } from '../../../selectors'; | ||
|
|
||
| export const NftGallery = () => { | ||
| const nfts = useSelector(getNfts); | ||
| const accounts = useSelector(getAccounts); | ||
|
|
||
| const accountsMap = new Map( | ||
| accounts.map((account) => [account.address, account]) | ||
| ); | ||
|
|
||
| const processedNfts = nfts.map((nft) => { | ||
| const metadata = JSON.parse(nft.metadata || '{}'); | ||
| const owner = accountsMap.get(nft.owner); | ||
| const rarity = calculateRarity(nft); | ||
| const estimatedValue = calculateEstimatedValue(nft, metadata); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Functions called before declaration causes ReferenceErrorHigh Severity The functions Additional Locations (1) |
||
|
|
||
| return { | ||
| ...nft, | ||
| metadata, | ||
| owner, | ||
| rarity, | ||
| estimatedValue, | ||
| displayName: metadata.name || nft.name, | ||
| displayImage: metadata.image || nft.image, | ||
| }; | ||
| }); | ||
|
|
||
| const calculateRarity = (nft) => { | ||
| const attributes = nft.attributes || []; | ||
| let rarityScore = 0; | ||
|
|
||
| attributes.forEach((attr) => { | ||
| const traitRarity = 1 / (attr.trait_count || 1); | ||
| rarityScore += traitRarity * 100; | ||
| }); | ||
|
|
||
| return rarityScore; | ||
| }; | ||
|
|
||
| const calculateEstimatedValue = (nft, metadata) => { | ||
| const baseValue = nft.floor_price || 0; | ||
| const rarityMultiplier = (nft.rarity_rank || 1) / 1000; | ||
| const attributeBonus = (metadata.attributes?.length || 0) * 0.1; | ||
|
|
||
| return baseValue * (1 + rarityMultiplier + attributeBonus); | ||
| }; | ||
|
|
||
| const sortedByRarity = [...processedNfts].sort((a, b) => b.rarity - a.rarity); | ||
| const sortedByValue = [...processedNfts].sort((a, b) => b.estimatedValue - a.estimatedValue); | ||
| const recentNfts = [...processedNfts].sort((a, b) => | ||
| new Date(b.created_at).getTime() - new Date(a.created_at).getTime() | ||
| ).slice(0, 10); | ||
|
|
||
| const collectionStats = processedNfts.reduce((acc, nft) => { | ||
| const collection = nft.collection; | ||
| if (!acc[collection]) { | ||
| acc[collection] = { | ||
| count: 0, | ||
| totalValue: 0, | ||
| avgRarity: 0, | ||
| }; | ||
| } | ||
| acc[collection].count++; | ||
| acc[collection].totalValue += nft.estimatedValue; | ||
| acc[collection].avgRarity += nft.rarity; | ||
| return acc; | ||
| }, {}); | ||
|
|
||
| Object.keys(collectionStats).forEach((collection) => { | ||
| collectionStats[collection].avgRarity /= collectionStats[collection].count; | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="nft-gallery"> | ||
| <div className="gallery-header"> | ||
| <h2>NFT Gallery ({processedNfts.length} items)</h2> | ||
| </div> | ||
|
|
||
| <div className="gallery-sections"> | ||
| <section className="top-rarity"> | ||
| <h3>Rarest NFTs</h3> | ||
| {sortedByRarity.slice(0, 20).map((nft, index) => ( | ||
| <div key={index} className="nft-card"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Array index as key breaks React reconciliationMedium Severity Using array Additional Locations (2) |
||
| <img src={nft.displayImage} alt={nft.displayName} /> | ||
| <div className="nft-details"> | ||
| <h4>{nft.displayName}</h4> | ||
| <p>Rarity: {nft.rarity.toFixed(2)}</p> | ||
| <p>Value: ${nft.estimatedValue.toFixed(2)}</p> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </section> | ||
|
|
||
| <section className="top-value"> | ||
| <h3>Most Valuable NFTs</h3> | ||
| {sortedByValue.slice(0, 20).map((nft, index) => ( | ||
| <div key={index} className="nft-card"> | ||
| <img src={nft.displayImage} alt={nft.displayName} /> | ||
| <div className="nft-details"> | ||
| <h4>{nft.displayName}</h4> | ||
| <p>Value: ${nft.estimatedValue.toFixed(2)}</p> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </section> | ||
|
|
||
| <section className="recent"> | ||
| <h3>Recent Acquisitions</h3> | ||
| {recentNfts.map((nft, index) => ( | ||
| <div key={index} className="nft-card"> | ||
| <img src={nft.displayImage} alt={nft.displayName} /> | ||
| <div className="nft-details"> | ||
| <h4>{nft.displayName}</h4> | ||
| <p>Added: {new Date(nft.created_at).toLocaleDateString()}</p> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </section> | ||
| </div> | ||
|
|
||
| <div className="collection-stats"> | ||
| <h3>Collection Statistics</h3> | ||
| {Object.entries(collectionStats).map(([collection, stats], index) => ( | ||
| <div key={index} className="collection-stat"> | ||
| <h4>{collection}</h4> | ||
| <p>Items: {stats.count}</p> | ||
| <p>Total Value: ${stats.totalValue.toFixed(2)}</p> | ||
| <p>Avg Rarity: {stats.avgRarity.toFixed(2)}</p> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import React, { useState, useEffect } from 'react'; | ||
| import { useSelector } from 'react-redux'; | ||
| import { getTokens, getBalances } from '../../selectors'; | ||
|
|
||
| export const AssetListFull = () => { | ||
| const tokens = useSelector(getTokens); | ||
| const balances = useSelector(getBalances); | ||
| const [allAssets, setAllAssets] = useState([]); | ||
| const [loading, setLoading] = useState(true); | ||
|
|
||
| useEffect(() => { | ||
| const fetchAllAssets = async () => { | ||
| const mockAssets = Array.from({ length: 5000 }, (_, i) => ({ | ||
| id: `asset-${i}`, | ||
| name: `Token ${i}`, | ||
| symbol: `TKN${i}`, | ||
| balance: Math.random() * 1000, | ||
| price: Math.random() * 100, | ||
| address: `0x${i.toString(16).padStart(40, '0')}`, | ||
| })); | ||
|
|
||
| setAllAssets(mockAssets); | ||
| setLoading(false); | ||
| }; | ||
|
|
||
| fetchAllAssets(); | ||
| }, []); | ||
|
|
||
| const enrichedAssets = allAssets.map((asset) => { | ||
| const accountsMap = new Map(); | ||
| tokens.forEach((token) => { | ||
| accountsMap.set(token.address, token); | ||
| }); | ||
|
|
||
| const balanceData = balances[asset.address] || '0'; | ||
| const fiatValue = parseFloat(asset.balance) * asset.price; | ||
|
|
||
| return { | ||
| ...asset, | ||
| balanceData, | ||
| fiatValue, | ||
| formatted: `${asset.symbol}: ${fiatValue.toFixed(2)}`, | ||
| }; | ||
| }); | ||
|
|
||
| const sortedAssets = enrichedAssets | ||
| .filter((asset) => parseFloat(asset.balance) > 0) | ||
| .sort((a, b) => b.fiatValue - a.fiatValue); | ||
|
|
||
| if (loading) { | ||
| return <div>Loading...</div>; | ||
| } | ||
|
|
||
| return ( | ||
| <div className="asset-list-full"> | ||
| <h2>All Assets ({sortedAssets.length})</h2> | ||
| <div className="asset-list-container"> | ||
| {sortedAssets.map((asset, index) => ( | ||
| <div key={index} className="asset-item"> | ||
| <div className="asset-info"> | ||
| <span className="asset-name">{asset.name}</span> | ||
| <span className="asset-symbol">{asset.symbol}</span> | ||
| </div> | ||
| <div className="asset-balance"> | ||
| <span>{asset.balance.toFixed(4)}</span> | ||
| <span>${asset.fiatValue.toFixed(2)}</span> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import React from 'react'; | ||
| import { Route, Switch } from 'react-router-dom'; | ||
| import Settings from '../settings'; | ||
| import Tokens from '../tokens'; | ||
| import Activity from '../activity'; | ||
| import Swap from '../swap'; | ||
| import Bridge from '../bridge'; | ||
| import Send from '../send'; | ||
| import Receive from '../receive'; | ||
| import ConnectedSites from '../connected-sites'; | ||
| import AssetDetails from '../asset-details'; | ||
| import { AssetListFull } from '../asset-list-full/asset-list-full'; | ||
| import { NftGallery } from '../../components/app/nft-gallery/nft-gallery'; | ||
| import ImportToken from '../import-token'; | ||
| import AddNetwork from '../add-network'; | ||
| import ConfirmTransaction from '../confirm-transaction'; | ||
| import ConnectHardwareWallet from '../connect-hardware-wallet'; | ||
|
|
||
| export const RoutesUnoptimized = () => { | ||
| return ( | ||
| <div className="main-container"> | ||
| <Switch> | ||
| <Route path="/settings" component={Settings} /> | ||
| <Route path="/tokens" component={Tokens} /> | ||
| <Route path="/activity" component={Activity} /> | ||
| <Route path="/swap" component={Swap} /> | ||
| <Route path="/bridge" component={Bridge} /> | ||
| <Route path="/send" component={Send} /> | ||
| <Route path="/receive" component={Receive} /> | ||
| <Route path="/connected-sites" component={ConnectedSites} /> | ||
| <Route path="/asset/:id" component={AssetDetails} /> | ||
| <Route path="/assets-all" component={AssetListFull} /> | ||
| <Route path="/nft-gallery" component={NftGallery} /> | ||
| <Route path="/import-token" component={ImportToken} /> | ||
| <Route path="/add-network" component={AddNetwork} /> | ||
| <Route path="/confirm-transaction" component={ConfirmTransaction} /> | ||
| <Route path="/connect-hardware" component={ConnectHardwareWallet} /> | ||
| <Route path="/" component={Tokens} /> | ||
| </Switch> | ||
| </div> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unprotected JSON.parse crashes on malformed metadata
Medium Severity
JSON.parse(nft.metadata || '{}')will throw aSyntaxErrorand crash the component if any NFT has a non-empty but malformed JSON string inmetadata. The|| '{}'fallback only handlesundefined/null/empty cases, not invalid JSON like"not json"or"{broken". Since NFT metadata often comes from external sources (blockchain, IPFS, APIs), malformed strings are a realistic scenario. A single corrupted NFT will crash the entire gallery with no error boundary or try-catch protection.