Cryptocurrency Real-Time Dashboard
Build a professional, real-time cryptocurrency dashboard that tracks the top 100 coins using the CoinGecko API. You will implement live price charts, portfolio tracking, and dark mode.
The Problem
Many beginner React tutorials focus on static data. To become a mid-level engineer, you must know how to handle complex, rapidly changing asynchronous data streams, manage caching, and render heavy data visualizations without freezing the browser.
Real-World Use Case
This architecture is identical to how financial tech (FinTech) companies build internal trading terminals, analytics dashboards, and consumer-facing portfolio apps (like Coinbase or Binance).
Technology Stack
React 18
For the core UI library and functional component architecture.
React Query
To manage server state, caching, and auto-refetching of live coin prices.
Tailwind CSS
For rapid, utility-first styling and easy Dark Mode implementation.
Recharts
A composable charting library built on React components for rendering price graphs.
Architecture & Design
Folder Structure
src/
├── components/
│ ├── CoinTable.tsx
│ ├── PriceChart.tsx
│ └── PortfolioWidget.tsx
├── hooks/
│ ├── useCoins.ts
│ └── usePortfolio.ts
├── services/
│ └── api.ts
└── App.tsxAPI Design
GET /api/v3/coins/marketsFetches top 100 coins with current prices and market cap.
GET /api/v3/coins/{id}/market_chartFetches 7-day historical price data for the chart.
Step-by-Step Implementation
Setup React Query and API Service
Initialize the query client to manage all of our external API requests.
import { QueryClient, QueryClientProvider } from 'react-query';
import { App } from './App';
// We configure the client to not retry immediately on failure
// and to keep data fresh for 60 seconds to avoid API rate limits.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60000,
retry: 1,
},
},
});
export default function Root() {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
}Code Explanation
React Query acts as a global cache for your server data. By setting `staleTime: 60000`, we ensure we don't spam the CoinGecko API every time a component re-renders.
Create the useCoins Custom Hook
Abstract the data fetching logic away from the UI components.
import { useQuery } from 'react-query';
const fetchCoins = async () => {
const res = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100');
if (!res.ok) throw new Error('Network response was not ok');
return res.json();
};
export const useCoins = () => {
return useQuery('coins', fetchCoins, {
// Auto refresh data every 60 seconds
refetchInterval: 60000,
});
};Code Explanation
By wrapping `useQuery` in a custom hook, any component in our app can simply call `const { data } = useCoins();` without worrying about the actual fetch logic or caching.
Render the Data Table
Map over the cached data and render the coin list.
import { useCoins } from '../hooks/useCoins';
export function CoinTable() {
const { data: coins, isLoading, error } = useCoins();
if (isLoading) return <div>Loading market data...</div>;
if (error) return <div>Error fetching data. Check your connection.</div>;
return (
<table className="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th>Coin</th>
<th>Price</th>
<th>24h Change</th>
</tr>
</thead>
<tbody>
{coins.map((coin) => (
<tr key={coin.id}>
<td>{coin.name}</td>
<td>${coin.current_price.toLocaleString()}</td>
<td className={coin.price_change_percentage_24h > 0 ? 'text-green-500' : 'text-red-500'}>
{coin.price_change_percentage_24h.toFixed(2)}%
</td>
</tr>
))}
</tbody>
</table>
);
}Code Explanation
This table automatically re-renders whenever React Query fetches new data in the background. The `toLocaleString()` method is used to format the currency nicely with commas.
Common Errors
If you hit CORS issues during development with CoinGecko, ensure you are not passing complex custom headers in your fetch request, as public APIs often block them.
CoinGecko has a strict rate limit for free tiers. Increase the `staleTime` and `refetchInterval` in React Query to 60000ms (1 minute).
Security & Performance
Never store API keys in the frontend code. Fortunately, the free CoinGecko API doesn't require one, but if you upgrade to Pro, you must route requests through a Next.js or Node.js backend.
Sanitize any inputs if you add a 'Search Coin' feature to prevent XSS attacks.
Use React's `memo` or `useMemo` on the Recharts component. Charts are very heavy to render, and you don't want them re-rendering if the user just clicks a dark-mode toggle.
Implement windowing/virtualization (e.g., using `react-window`) if you decide to load all 10,000+ coins instead of just the top 100.
Interview Questions
Q: Why did you use React Query instead of `useEffect` for fetching?
A: useEffect requires manual management of loading states, error states, and caching. React Query handles deduplication, background refetching, and stale data automatically, resulting in less boilerplate and better UX.
Q: How would you handle real-time price updates (every second)?
A: Instead of polling the REST API every second (which would hit rate limits), I would open a WebSocket connection to a crypto provider like Binance, and update the global state whenever a message is received.