Building a Real-Time Weather Widget in React with WeatherAPI
Weather widgets are essential components in many web applications, from dashboards to travel sites. In this tutorial, we’ll build a responsive React weather widget using WeatherAPI that handles loading states, error management, and real-time data updates with modern React patterns.
Setting Up Your React Weather Widget
First, let’s create a custom hook to manage our weather data. This approach separates concerns and makes our component reusable across different parts of your application.
// hooks/useWeatherData.js
import { useState, useEffect } from 'react';
const useWeatherData = (location, refreshInterval = 300000) => { // 5 minutes
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchWeather = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${process.env.REACT_APP_WEATHER_API_KEY}&q=${location}&aqi=yes`
);
if (!response.ok) {
throw new Error(`Weather API error: ${response.status}`);
}
const data = await response.json();
setWeather(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (location) {
fetchWeather();
// Set up auto-refresh
const interval = setInterval(fetchWeather, refreshInterval);
return () => clearInterval(interval);
}
}, [location, refreshInterval]);
return { weather, loading, error, refetch: fetchWeather };
};
export default useWeatherData;
Creating the Weather Widget Component
Now let’s build the main widget component with proper loading states and error handling:
// components/WeatherWidget.js
import React from 'react';
import useWeatherData from '../hooks/useWeatherData';
import './WeatherWidget.css';
const WeatherWidget = ({ location = 'London', className = '' }) => {
const { weather, loading, error, refetch } = useWeatherData(location);
if (loading) {
return (
<div className={`weather-widget loading ${className}`}>
<div className="loading-spinner"></div>
<p>Loading weather data...</p>
</div>
);
}
if (error) {
return (
<div className={`weather-widget error ${className}`}>
<p>Error: {error}</p>
<button onClick={refetch} className="retry-button">
Try Again
</button>
</div>
);
}
const { location: loc, current } = weather;
return (
<div className={`weather-widget ${className}`}>
<div className="weather-header">
<h3>{loc.name}, {loc.country}</h3>
<button onClick={refetch} className="refresh-button">
↻
</button>
</div>
<div className="weather-main">
<div className="temperature-section">
<img
src={current.condition.icon}
alt={current.condition.text}
className="weather-icon"
/>
<div className="temperature">
<span className="temp-value">{Math.round(current.temp_c)}</span>
<span className="temp-unit">°C</span>
</div>
</div>
<div className="weather-details">
<p className="condition">{current.condition.text}</p>
<p className="feels-like">
Feels like {Math.round(current.feelslike_c)}°C
</p>
</div>
</div>
<div className="weather-stats">
<div className="stat">
<span className="label">Humidity</span>
<span className="value">{current.humidity}%</span>
</div>
<div className="stat">
<span className="label">Wind</span>
<span className="value">{current.wind_kph} km/h</span>
</div>
<div className="stat">
<span className="label">UV Index</span>
<span className="value">{current.uv}</span>
</div>
<div className="stat">
<span className="label">Air Quality</span>
<span className="value">
{current.air_quality ? current.air_quality.pm2_5.toFixed(1) : 'N/A'}
</span>
</div>
</div>
<div className="last-updated">
Last updated: {new Date(current.last_updated).toLocaleTimeString()}
</div>
</div>
);
};
export default WeatherWidget;
Enhanced Widget with Location Search
Let’s create an enhanced version that allows users to search for different locations:
// components/WeatherWidgetWithSearch.js
import React, { useState } from 'react';
import useWeatherData from '../hooks/useWeatherData';
const WeatherWidgetWithSearch = () => {
const [location, setLocation] = useState('London');
const [searchInput, setSearchInput] = useState('');
const { weather, loading, error } = useWeatherData(location);
const handleSearch = (e) => {
e.preventDefault();
if (searchInput.trim()) {
setLocation(searchInput.trim());
setSearchInput('');
}
};
return (
<div className="weather-widget-container">
<form onSubmit={handleSearch} className="search-form">
<input
type="text"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
placeholder="Enter city name..."
className="search-input"
/>
<button type="submit" disabled={loading} className="search-button">
{loading ? 'Loading...' : 'Search'}
</button>
</form>
{/* Render the weather widget here */}
{weather && !loading && !error && (
<WeatherWidget location={location} />
)}
</div>
);
};
Adding Geolocation Support
Enhance your widget with automatic location detection using the browser’s geolocation API combined with WeatherAPI’s IP geolocation feature:
// hooks/useGeolocation.js
import { useState, useEffect } from 'react';
const useGeolocation = () => {
const [location, setLocation] = useState(null);
useEffect(() => {
// Try browser geolocation first
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
setLocation(`${latitude},${longitude}`);
},
() => {
// Fallback to IP geolocation
setLocation('auto:ip');
}
);
} else {
setLocation('auto:ip');
}
}, []);
return location;
};
Environment Setup and Usage
Don’t forget to add your WeatherAPI key to your environment variables:
# .env
REACT_APP_WEATHER_API_KEY=your_api_key_here
Then use your widget in your main application:
// App.js
import WeatherWidget from './components/WeatherWidget';
function App() {
return (
<div className="App">
<WeatherWidget location="New York" />
<WeatherWidget location="Tokyo" />
</div>
);
}
Performance and Production Considerations
For production applications, consider implementing request caching to avoid hitting rate limits and improve performance. WeatherAPI provides 100,000 free calls per month, making it perfect for development and small to medium applications.
The widget automatically refreshes every 5 minutes, but you can customize this interval based on your needs. For real-time applications requiring more frequent updates, consider upgrading to WeatherAPI’s paid plans for higher rate limits.
Ready to start building? Sign up for your free WeatherAPI key and get 100,000 API calls per month with no credit card required. With coverage of 4M+ locations and average response times under 200ms, WeatherAPI powers weather widgets for 850,000+ developers worldwide.
