By the end of this lesson, you will:
💡 Why Cache? Imagine if your weather app had to reload everything every time you opened it! Caching saves data locally so your app works faster and even works offline.
Local storage is like having a small storage room in your user's browser where you can keep data permanently (until they clear it).
Storage Type | Lifespan | Size Limit | Shared Between Tabs |
---|---|---|---|
localStorage | Until cleared | ~5-10MB | ✅ Yes |
sessionStorage | Until tab closes | ~5-10MB | ❌ No |
cookies | Set expiration | ~4KB | ✅ Yes |
// Save a simple value
localStorage.setItem('username', 'Sarah');
localStorage.setItem('theme', 'dark');
localStorage.setItem('lastVisit', new Date().toISOString());
// Save numbers (stored as strings)
localStorage.setItem('score', '1250');
// Get saved values
const username = localStorage.getItem('username');
const theme = localStorage.getItem('theme');
const score = parseInt(localStorage.getItem('score')); // Convert back to number
// Check if something exists
if (localStorage.getItem('username')) {
console.log('User has visited before!');
}
// Remove one item
localStorage.removeItem('score');
// Clear everything (careful!)
localStorage.clear();
Let's make your weather app remember user settings:
class UserPreferences {
static save(preferences) {
localStorage.setItem('weatherPrefs', JSON.stringify(preferences));
}
static load() {
const saved = localStorage.getItem('weatherPrefs');
return saved ? JSON.parse(saved) : {
units: 'metric',
defaultCity: 'London',
theme: 'light',
notifications: true
};
}
static updateSetting(key, value) {
const prefs = this.load();
prefs[key] = value;
this.save(prefs);
}
}
// Usage in your weather app
UserPreferences.updateSetting('units', 'imperial');
UserPreferences.updateSetting('defaultCity', 'New York');
const currentPrefs = UserPreferences.load();
console.log(`Temperature in ${currentPrefs.units}`);
localStorage only stores strings, so we use JSON for complex data:
// Weather data object
const weatherData = {
city: 'Paris',
temperature: 22,
humidity: 65,
forecast: [
{ day: 'Mon', temp: 24, icon: 'sunny' },
{ day: 'Tue', temp: 19, icon: 'cloudy' }
],
lastUpdated: new Date().toISOString()
};
// Save as JSON string
localStorage.setItem('weather', JSON.stringify(weatherData));
// Load and parse back to object
const saved = localStorage.getItem('weather');
const parsedWeather = saved ? JSON.parse(saved) : null;
console.log(parsedWeather.city); // "Paris"
Cache API data to make your app faster and work offline:
class WeatherCache {
static CACHE_DURATION = 10 * 60 * 1000; // 10 minutes in milliseconds
static getCacheKey(city) {
return `weather_${city}`;
}
static save(city, weatherData) {
const cacheData = {
data: weatherData,
timestamp: Date.now(),
city: city
};
localStorage.setItem(this.getCacheKey(city), JSON.stringify(cacheData));
}
static load(city) {
const cached = localStorage.getItem(this.getCacheKey(city));
if (!cached) return null;
const cacheData = JSON.parse(cached);
// Check if cache is still fresh
const age = Date.now() - cacheData.timestamp;
if (age > this.CACHE_DURATION) {
this.remove(city); // Clean up expired cache
return null;
}
return cacheData.data;
}
static remove(city) {
localStorage.removeItem(this.getCacheKey(city));
}
static isExpired(city) {
const cached = localStorage.getItem(this.getCacheKey(city));
if (!cached) return true;
const cacheData = JSON.parse(cached);
const age = Date.now() - cacheData.timestamp;
return age > this.CACHE_DURATION;
}
}
Combine caching with API calls for the best experience:
async function getWeatherData(city) {
// First, try to get from cache
const cached = WeatherCache.load(city);
if (cached) {
console.log('Using cached data for', city);
return cached;
}
try {
// Cache miss - fetch from API
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY`);
const weatherData = await response.json();
// Save to cache for next time
WeatherCache.save(city, weatherData);
return weatherData;
} catch (error) {
// Return stale cache as fallback
const staleCache = localStorage.getItem(WeatherCache.getCacheKey(city));
if (staleCache) return JSON.parse(staleCache).data;
throw error;
}
}
Different strategies for when to refresh cached data:
class SmartCache {
// Time-based expiration
static isTimeExpired(timestamp, maxAge) {
return (Date.now() - timestamp) > maxAge;
}
// Manual refresh
static markForRefresh(key) {
localStorage.setItem(`${key}_refresh`, 'true');
}
static needsRefresh(key) {
return localStorage.getItem(`${key}_refresh`) === 'true';
}
}
// Usage: Mark for refresh when user pulls to refresh
SmartCache.markForRefresh('weather_Tokyo');
// Next request will ignore cache if marked for refresh
if (SmartCache.needsRefresh('weather_Tokyo')) {
// Force fresh fetch
}
Make your app work even without internet:
class OfflineWeather {
static isOnline() {
return navigator.onLine;
}
static async getWeather(city) {
if (this.isOnline()) {
try {
// Online: try to fetch fresh data
return await getWeatherData(city);
} catch (error) {
// API failed, fall back to cache
return this.getOfflineWeather(city);
}
} else {
// Offline: use cached data only
return this.getOfflineWeather(city);
}
}
static getOfflineWeather(city) {
const cached = WeatherCache.load(city);
if (cached) {
// Mark as offline data
cached.isOffline = true;
return cached;
}
throw new Error('No offline data available for ' + city);
}
static getAvailableOfflineCities() {
const cities = [];
// Check all localStorage keys for weather data
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('weather_')) {
const city = key.replace('weather_', '');
cities.push(city);
}
}
return cities;
}
}
// Listen for online/offline changes
window.addEventListener('online', () => {
console.log('Back online! Refreshing data...');
});
window.addEventListener('offline', () => {
console.log('Gone offline. Using cached data.');
});
localStorage has size limits (~5-10MB). Here's how to manage it:
class StorageManager {
static getStorageSize() {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage[key].length + key.length;
}
}
return total;
}
static cleanupOldCache() {
const keys = Object.keys(localStorage);
const weatherKeys = keys.filter(key => key.startsWith('weather_'));
// Remove oldest entries if we have too many
if (weatherKeys.length > 10) {
const toRemove = weatherKeys.slice(0, weatherKeys.length - 10);
toRemove.forEach(key => localStorage.removeItem(key));
}
}
static isStorageFull() {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
return false;
} catch (e) {
return true;
}
}
}
Create a weather dashboard that remembers everything:
// Your task: Complete this WeatherDashboard class
class WeatherDashboard {
constructor() {
this.loadUserPreferences();
this.checkOnlineStatus();
}
loadUserPreferences() {
// TODO: Load saved preferences
// TODO: Apply theme, units, default city
}
async displayWeather(city) {
// TODO: Get weather data (cached or fresh)
// TODO: Display with user's preferred units
// TODO: Show offline indicator if needed
}
savePreference(key, value) {
// TODO: Save user preference
// TODO: Apply immediately to UI
}
clearAllData() {
// TODO: Clear cache and preferences
// TODO: Ask user for confirmation first
}
}
// Test your implementation
const dashboard = new WeatherDashboard();
dashboard.displayWeather('London');
dashboard.savePreference('units', 'imperial');
In the next lesson, we'll explore Real-time Communication where you'll learn about WebSockets, webhooks, and live data updates. Perfect for chat apps and live dashboards!