Skip to main content
The app fetches data from two public APIs — CoinGecko and CryptoCompare. Both impose rate limits on unauthenticated requests. Without caching, every page visit or component mount triggers a fresh network request, which wastes bandwidth, slows the UI, and risks hitting those limits during normal use. The solution is a thin caching layer in src/services/cache.js that stores API responses in localStorage alongside a timestamp. On the next request the cache is checked first; only if the data is older than five minutes does the app hit the network again.

TTL check

Every cache function applies the same age check before deciding whether to serve cached data or fetch fresh data:
Date.now() - Number(cacheDate) < 5 * 60 * 1000
Date.now() returns milliseconds since the Unix epoch. cacheDate is the timestamp stored as a string when the data was last fetched. Converting it with Number() and comparing the difference to 300 000 ms (5 minutes) keeps the logic self-contained and dependency-free.

Cache functions

Each function follows the same read-check-write pattern. The only differences are the localStorage key names and the underlying API call.

keepInfo()

Caches the top 20 cryptocurrencies by market cap fetched from CoinGecko.
export async function keepInfo() {
  const cache = localStorage.getItem("getInfoCrypto");
  const cacheDate = localStorage.getItem("getInfoCryptoDate");

  if (cache && cacheDate && Date.now() - Number(cacheDate) < 5 * 60 * 1000) {
    return JSON.parse(cache);
  } else {
    const data = await getTopCryptos();
    localStorage.setItem("getInfoCrypto", JSON.stringify(data));
    localStorage.setItem("getInfoCryptoDate", Date.now());
    return data;
  }
}
localStorage keyContents
getInfoCryptoSerialised array of top-20 coin objects
getInfoCryptoDateUnix timestamp (ms) of last fetch

keepNews()

Caches Spanish-language market news from CryptoCompare.
export async function keepNews() {
  const cache = localStorage.getItem("getNewsCrypto");
  const cacheDate = localStorage.getItem("getNewsCryptoDate");

  if (cache && cacheDate && Date.now() - Number(cacheDate) < 5 * 60 * 1000) {
    return JSON.parse(cache);
  } else {
    const data = await newsApi();
    localStorage.setItem("getNewsCrypto", JSON.stringify(data));
    localStorage.setItem("getNewsCryptoDate", Date.now());
    return data;
  }
}
localStorage keyContents
getNewsCryptoSerialised news response object
getNewsCryptoDateUnix timestamp (ms) of last fetch

keepPrice()

Caches spot prices for a fixed set of coins (Bitcoin, Ethereum, Solana, Cardano, Tether, BNB) used by the converter page.
export async function keepPrice() {
  const cache = localStorage.getItem("getPriceCrypto");
  const cacheDate = localStorage.getItem("getPriceCryptoDate");

  if (cache && cacheDate && Date.now() - Number(cacheDate) < 5 * 60 * 1000) {
    return JSON.parse(cache);
  } else {
    const data = await priceApi();
    localStorage.setItem("getPriceCrypto", JSON.stringify(data));
    localStorage.setItem("getPriceCryptoDate", Date.now());
    return data;
  }
}
localStorage keyContents
getPriceCryptoSerialised price map ({ bitcoin: { usd: ... }, ... })
getPriceCryptoDateUnix timestamp (ms) of last fetch

keepHistory(cryptoId, days)

Caches historical price chart data. Because every coin and time-range combination is a distinct dataset, the key is dynamic.
export async function keepHistory(cryptoId, days) {
  const key = `history_${cryptoId}_${days}`;
  const cache = localStorage.getItem(key);
  const cacheDate = localStorage.getItem(`${key}Date`);

  if (cache && cacheDate && Date.now() - Number(cacheDate) < 5 * 60 * 1000) {
    return JSON.parse(cache);
  } else {
    const data = await getHistoryCrypto({ cryptoId, days });
    localStorage.setItem(key, JSON.stringify(data));
    localStorage.setItem(`${key}Date`, Date.now());
    return data;
  }
}
For example, fetching Bitcoin’s 7-day history writes two keys: history_bitcoin_7 and history_bitcoin_7Date. Switching to a 30-day view writes a separate pair, so both remain cached independently.

Cache lookup flow

1

Read localStorage

The cache function reads the data key and its companion timestamp key from localStorage.
2

Check age

If both values exist and Date.now() - Number(cacheDate) < 300 000, the cached data is still fresh.
3

Serve from cache

The cached string is parsed with JSON.parse() and returned immediately — no network request is made.
4

Fetch on cache miss

If the cache is empty, missing, or expired, the underlying API function is called (getTopCryptos, newsApi, priceApi, or getHistoryCrypto).
5

Store and return

The fresh response is written to localStorage as a JSON string alongside a new timestamp, then returned to the caller.

Trade-offs

localStorage is scoped to the origin and the browser profile. A user opening the app in a different browser, a private/incognito window, or on a different device starts with an empty cache and will trigger fresh API calls.
If a user has the app open in two tabs, each tab manages its own in-memory state. When one tab refreshes the cache, the other tab does not know until it runs its own cache check. In practice this is harmless because price data is not user-generated.
Calling localStorage.clear() (e.g., from browser DevTools or another script on the same origin) removes all cached data. The next load fetches everything fresh.
Browsers cap localStorage at ~5 MB per origin. Historical chart data for many coins and ranges can accumulate. If storage fills up, localStorage.setItem throws a QuotaExceededError and the cache write silently fails in the current implementation.
The 5-minute TTL is hardcoded. Crypto prices can move significantly within that window. For production use, consider making the TTL configurable per data type — shorter for prices, longer for news.

Build docs developers (and LLMs) love