Skip to content

Commit

Permalink
refactor: improve loading states and initial render performance
Browse files Browse the repository at this point in the history
  • Loading branch information
rhochmayr committed Oct 26, 2024
1 parent 153d0eb commit 2763f03
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 83 deletions.
8 changes: 8 additions & 0 deletions src/components/WalletStatusGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export function WalletStatusGrid({
const currentUTCHour = now.getUTCHours();
const isCurrentDate = selectedDate.getTime() === currentUTCDate.getTime();

if (wallets.length === 0) {
return (
<div className="p-4 text-center text-muted-foreground">
No nodes added yet. Add a node using the form above.
</div>
);
}

return (
<div className="p-4 space-y-4">
<WalletGridHeader />
Expand Down
6 changes: 3 additions & 3 deletions src/components/wallet/WalletRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
import { X, Pencil } from 'lucide-react';
import { X, Pencil, Loader2 } from 'lucide-react';
import { NodeMetrics } from '../node/NodeMetrics';
import { WalletHourCell } from './WalletHourCell';
import type { NodeStatus } from '@/types';
Expand Down Expand Up @@ -124,8 +124,8 @@ export function WalletRow({
</div>

{wallet.isLoading ? (
<div className="col-span-24 flex justify-center">
<div className="animate-spin rounded-full h-4 w-4 border-2 border-primary border-t-transparent" />
<div className="col-span-24 flex justify-center items-center">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
) : (
wallet.hours?.map((hour) => (
Expand Down
186 changes: 108 additions & 78 deletions src/hooks/useWalletData.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { toast } from 'sonner';
import { fetchWalletTransactions, groupTransactionsByDate, getHourlyTransactions, fetchNodeMetrics, fetchNodeStats } from '@/lib/api';
import { loadFromStorage, saveToStorage } from '@/lib/storage';
import { TIME } from '@/constants';
import type { WalletData, NodeMetrics, NodeStats } from '@/types';

const REFRESH_INTERVAL = 300000; // 5 minutes

export function useWalletData(date: Date) {
const [walletsData, setWalletsData] = useState<Record<string, WalletData>>({});
const [nodeMetricsData, setNodeMetricsData] = useState<Record<string, NodeMetrics>>({});
const [nodeStatsData, setNodeStatsData] = useState<Record<string, NodeStats>>({});
const [isRefreshing, setIsRefreshing] = useState(false);
const [isInitialized, setIsInitialized] = useState(false);
const [availableDates, setAvailableDates] = useState<Date[]>([]);
const initializationInProgress = useRef(false);

const updateDisplayedData = useCallback((selectedDate: Date) => {
setWalletsData((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((address) => {
if (!address) return;
const hours = getHourlyTransactions(
updated[address].transactions,
selectedDate
);
updated[address] = {
...updated[address],
hours: Array.from({ length: 24 }, (_, i) => ({
hour: i,
transactions: hours[i],
})),
};
});
return updated;
});
}, []);

const initializeWallet = useCallback(
async (address: string, name?: string) => {
const normalizedAddress = address.toLowerCase();

setWalletsData((prev) => ({
...prev,
[normalizedAddress]: {
...prev[normalizedAddress],
address: normalizedAddress,
name,
isLoading: true,
},
}));

try {
const transactions = await fetchWalletTransactions(normalizedAddress);
const transactionsByDate = groupTransactionsByDate(transactions);
Expand All @@ -25,24 +57,29 @@ export function useWalletData(date: Date) {
setWalletsData((prev) => ({
...prev,
[normalizedAddress]: {
address: normalizedAddress,
name,
...prev[normalizedAddress],
transactions,
transactionsByDate,
hours: Array.from({ length: 24 }, (_, i) => ({
hour: i,
transactions: hours[i],
})),
metrics: nodeMetricsData[normalizedAddress],
stats: nodeStatsData[normalizedAddress],
isLoading: false,
},
}));
} catch (error) {
console.error('Error initializing wallet:', error);
setWalletsData((prev) => ({
...prev,
[normalizedAddress]: {
...prev[normalizedAddress],
isLoading: false,
error: 'Failed to load wallet data',
},
}));
}
},
[date, nodeMetricsData, nodeStatsData]
[date]
);

const refreshWallets = useCallback(async () => {
Expand All @@ -54,6 +91,17 @@ export function useWalletData(date: Date) {
let toastId: string | number | undefined;

try {
setWalletsData((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((address) => {
updated[address] = {
...updated[address],
isLoading: true,
};
});
return updated;
});

toastId = toast.loading(`Refreshing data...`, {
description: `Refreshing wallet transactions...`,
});
Expand All @@ -71,6 +119,7 @@ export function useWalletData(date: Date) {
...prev[normalizedAddress],
transactions,
transactionsByDate,
isLoading: false,
},
}));

Expand Down Expand Up @@ -107,6 +156,7 @@ export function useWalletData(date: Date) {
...updated[address],
metrics: metrics[address],
stats: stats[address],
isLoading: false,
};
});
return updated;
Expand All @@ -125,88 +175,68 @@ export function useWalletData(date: Date) {
} finally {
setIsRefreshing(false);
}
}, [walletsData, date, isInitialized]);

const updateDisplayedData = useCallback((selectedDate: Date) => {
setWalletsData((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((address) => {
if (!address) return;
const hours = getHourlyTransactions(
updated[address].transactions,
selectedDate
);
updated[address] = {
...updated[address],
hours: Array.from({ length: 24 }, (_, i) => ({
hour: i,
transactions: hours[i],
})),
};
});
return updated;
});
}, []);
}, [walletsData, date, isInitialized, updateDisplayedData]);

useEffect(() => {
const loadInitialData = async () => {
if (isInitialized) return;
async function loadInitialData() {
if (isInitialized || initializationInProgress.current) return;
initializationInProgress.current = true;

const savedData = loadFromStorage();

const toastId = toast.loading('Fetching node data...');
try {
const metrics = await fetchNodeMetrics();
const stats = await fetchNodeStats();
const savedData = loadFromStorage();
const [metrics, stats] = await Promise.all([
fetchNodeMetrics(),
fetchNodeStats()
]);

setNodeMetricsData(metrics);
setNodeStatsData(stats);

toast.success('Node data fetched', { id: toastId });
} catch (error) {
console.error('Error fetching node data:', error);
toast.error('Failed to fetch node data', { id: toastId });
}

const initialWallets = Object.entries(savedData.wallets).reduce(
(acc, [address, data]) => {
const normalizedAddress = address?.toLowerCase() || '';
if (!normalizedAddress) return acc;

return {
...acc,
[normalizedAddress]: {
address: normalizedAddress,
name: data.name,
transactions: [],
transactionsByDate: {},
hours: Array.from({ length: 24 }, (_, i) => ({
hour: i,
transactions: { type1: false, type2: false, transactions: [] },
})),
metrics: nodeMetricsData[normalizedAddress],
stats: nodeStatsData[normalizedAddress],
isLoading: true,
},
};
},
{} as Record<string, WalletData>
);
const initialWallets = Object.entries(savedData.wallets).reduce(
(acc, [address, data]) => {
const normalizedAddress = address?.toLowerCase() || '';
if (!normalizedAddress) return acc;

return {
...acc,
[normalizedAddress]: {
address: normalizedAddress,
name: data.name,
transactions: [],
transactionsByDate: {},
hours: Array.from({ length: 24 }, (_, i) => ({
hour: i,
transactions: { type1: false, type2: false, transactions: [] },
})),
metrics: metrics[normalizedAddress],
stats: stats[normalizedAddress],
isLoading: true,
},
};
},
{} as Record<string, WalletData>
);

setWalletsData(initialWallets);
setWalletsData(initialWallets);

const addresses = Object.keys(initialWallets);
await Promise.all(
addresses.map((address) =>
initializeWallet(address, savedData.wallets[address].name)
)
);
const addresses = Object.keys(initialWallets);
await Promise.all(
addresses.map((address) =>
initializeWallet(address, savedData.wallets[address].name)
)
);

setIsInitialized(true);
};
setIsInitialized(true);
} catch (error) {
console.error('Error during initial load:', error);
toast.error('Failed to load initial data');
} finally {
initializationInProgress.current = false;
}
}

loadInitialData();
}, [initializeWallet, date, isInitialized, nodeMetricsData, nodeStatsData]);
}, [initializeWallet, isInitialized]);

useEffect(() => {
if (!isInitialized) return;
Expand All @@ -215,7 +245,7 @@ export function useWalletData(date: Date) {

useEffect(() => {
if (!isInitialized) return;
const interval = setInterval(refreshWallets, REFRESH_INTERVAL);
const interval = setInterval(refreshWallets, TIME.REFRESH_INTERVAL);
return () => clearInterval(interval);
}, [refreshWallets, isInitialized]);

Expand Down
2 changes: 1 addition & 1 deletion src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function getHourlyTransactions(

export async function fetchNodeMetrics(): Promise<Record<string, NodeMetrics>> {
try {
const response = await fetch('https://api-testnet.lilypad.tech/metrics-dashboard/nodes');
const response = await fetch('https://jsondatapoint.blob.core.windows.net/jsondata/nodemetrics.json');
const data = await response.json();

return data.reduce((acc: Record<string, NodeMetrics>, node: any) => {
Expand Down
7 changes: 6 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';

createRoot(document.getElementById('root')!).render(<App />);
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);

0 comments on commit 2763f03

Please sign in to comment.