Understanding API Calls: Building a Weather App
A real developer explanation of how API calls work, from fetch to state management. No BS, just practical code.
Understanding API Calls: Building a Weather App
How API Calls Actually Work: A Real Dev Explanation
The Basics (No BS Version)
So you want to understand how those weather API calls work? Let me break it down the way I wish someone had explained it to me when I started.
The Complete Flow
Step 1: User Picks a City
Pretty straightforward. User clicks a button in the modal:
<button onClick={() => {
setSelectedCity(city); // Store the new city
setShowCitySelector(false); // Close modal
}}>
Nothing fancy. Just updating state. But here's where it gets interesting...
Step 2: useEffect Wakes Up
This is the magic part. React has this thing called useEffect that watches variables:
useEffect(() => {
fetchWeather(selectedCity);
}, [selectedCity]);
That array at the end? [selectedCity] - that's the dependency list. It's literally React saying:
"Hey, I'm watching selectedCity. If it changes, I'll run fetchWeather again."
Think of it like a guard dog. It sits there watching one thing. When that thing moves, it barks (runs your function).
Step 3: Building the API Request
Here's where we actually talk to the weather API:
const fetchWeather = async (city: City) => {
// Turn on the loading spinner
setLoading(true);
setError(''); // Clear old errors
try {
// Build the URL with all the data we need
const url = `https://api.open-meteo.com/v1/forecast?` +
`latitude=${city.lat}&` +
`longitude=${city.lon}&` +
`current=temperature_2m,relative_humidity_2m,...&` +
`daily=temperature_2m_max,temperature_2m_min,...&` +
`timezone=auto`;
// Make the actual request
const response = await fetch(url);
// Did it work?
if (!response.ok) {
throw new Error('API said no');
}
// Convert response to JavaScript object
const data = await response.json();
// Save it to state
setWeather({
...data,
cityName: city.name,
country: city.country
});
} catch (err) {
console.error('Error:', err);
setError('Could not get weather data');
} finally {
// Always turn off loading, error or not
setLoading(false);
}
};
Let me explain what's happening here:
The URL Construction
You're basically building a string with all the parameters the API needs:
- latitude and longitude - where on Earth we're asking about
- current - what current weather data we want (temp, humidity, etc.)
- daily - what forecast data we want (7-day max/min temps)
- timezone=auto - let the API figure out the timezone
The fetch() Call
fetch() is JavaScript's built-in way to make HTTP requests. It's async, meaning it takes time. That's why we use await - it pauses the function until the API responds.
Without await, your code would keep running while waiting for the response, and you'd try to use data that doesn't exist yet. Bad times.
The response.json() Part
The API sends back text (specifically JSON format). response.json() converts that text into a JavaScript object you can actually work with.
// What the API sends (text):
'{"temperature": 18.5}'
// What response.json() gives you (object):
{ temperature: 18.5 }
Error Handling
The try/catch/finally block is crucial:
- try: "Attempt this risky stuff"
- catch: "If anything fails, run this instead"
- finally: "No matter what happened, do this at the end"
This is why setLoading(false) is in finally - we want to hide the spinner whether we succeeded or failed.
What the API Actually Returns
When you hit that endpoint, you get back something like this:
{
"latitude": 40.4168,
"longitude": -3.7038,
"current": {
"time": "2024-12-04T15:00",
"temperature_2m": 18.5,
"apparent_temperature": 16.2,
"relative_humidity_2m": 65,
"precipitation": 0,
"weather_code": 2,
"wind_speed_10m": 12.5,
"pressure_msl": 1015.3
},
"daily": {
"time": ["2024-12-04", "2024-12-05", "2024-12-06", "..."],
"temperature_2m_max": [20, 22, 19, 21, 23, 20, 18],
"temperature_2m_min": [12, 14, 11, 13, 15, 12, 10],
"weather_code": [2, 0, 3, 1, 2, 3, 61]
}
}
See how the daily data is arrays? That's 7 days of forecasts. Index 0 is today, index 1 is tomorrow, etc.
The Visual Flow
User clicks "Barcelona"
↓
setSelectedCity(barcelona) updates state
↓
useEffect detects the change
↓
fetchWeather(barcelona) runs
↓
setLoading(true) - spinner appears
↓
Build the URL with Barcelona's coordinates
↓
fetch(url) - send HTTP request, WAIT for response
↓
API responds with JSON data
↓
response.json() - convert to JavaScript object
↓
setWeather(data) - save to state
↓
setLoading(false) - hide spinner
↓
React re-renders with new data
↓
User sees Barcelona's weather
Key Concepts You Need to Get
React State (useState)
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
State is how React remembers things between renders. When you call setWeather(), React:
- Updates the value
- Re-renders the component
- Shows the new data
It's not like a normal variable. Regular variables reset on every render. State persists.
The useEffect Hook
This one trips people up. Here's the deal:
useEffect(() => {
// This runs after the component renders
fetchWeather(selectedCity);
}, [selectedCity]); // Only re-run if this changes
When does it run?
- First render (always)
- Anytime
selectedCitychanges
Why use it?
Because fetching data is a "side effect" - something that affects the outside world (the API server). React wants you to declare these explicitly.
async/await Explained
// Without await (wrong):
const response = fetch(url);
console.log(response); // Promise { <pending> } - not what you want
// With await (right):
const response = await fetch(url);
console.log(response); // Response object with actual data
await literally pauses your function until the promise resolves. But you can only use await inside an async function, which is why we write:
const fetchWeather = async (city: City) => {
// Now we can use await inside here
}
Why This Pattern Works
Separation of Concerns
fetchWeatheronly fetches datauseEffectonly watches for changes- State only stores data
- UI only displays data
Everything has one job. Easy to debug, easy to change.
Predictable State
You always know what's happening:
loading === true→ show spinnererror !== ''→ show errorweather !== null→ show weather
No weird in-between states.
Automatic Updates
Change the city? React handles the rest:
useEffecttriggers- Data fetches
- State updates
- UI re-renders
You don't manually update the DOM anywhere. React does it.
A Simpler Example
If all that was too much, here's the absolute minimal version:
// 1. User picks a city
const city = { lat: 40.4168, lon: -3.7038, name: 'Madrid' };
// 2. Build URL
const url = `https://api.open-meteo.com/v1/forecast?latitude=40.4168&longitude=-3.7038¤t=temperature_2m`;
// 3. Fetch data
const response = await fetch(url);
const data = await response.json();
// 4. Show it
console.log(data.current.temperature_2m); // 18.5
// 5. Render
return <div>{data.current.temperature_2m}°</div>
That's it. Everything else is just handling errors, loading states, and making it production-ready.
Common Mistakes I Made
1. Forgetting async/await
// Wrong - fetch returns a promise, not data
const data = fetch(url).json();
// Right - wait for each step
const response = await fetch(url);
const data = await response.json();
2. Not checking response.ok
// Wrong - assumes it always works
const data = await response.json();
// Right - check for errors
if (!response.ok) throw new Error('Failed');
const data = await response.json();
3. Putting fetch in the component body
// Wrong - runs on every render, infinite loop
function Component() {
fetch(url); // DON'T DO THIS
return <div>...</div>
}
// Right - inside useEffect
function Component() {
useEffect(() => {
fetch(url);
}, []);
}
Why APIs Use Coordinates Instead of City Names,
You might wonder: why not just send the city name?
Accuracy: "Paris" could be Paris, France or Paris, Texas. Coordinates are exact.
No Translation: Works in any language without conversion.
Flexibility: Can get weather for ANY point on Earth, not just named cities.
Speed: The API doesn't need to look up city names in a database first.
Your frontend keeps the city names for display. The API just wants lat/lon.
Final Thoughts
API calls seem complex at first, but it's always the same pattern:
1 - Build URL with parameters
2 - Send request (fetch)
3 - Wait for response (await)
4 - Parse response (json)
5 - Update state
6 - React re-renders
Once you get this pattern, you can call any API. Weather, GitHub, Stripe, whatever. The only difference is what parameters you send and what data comes back. The key is understanding that it's all asynchronous. You send a request, wait for a response, then handle that response. Like ordering food - you don't stand at the counter waiting, you sit down and do other stuff until it arrives. Keep building, keep breaking things, and eventually this becomes second nature.
PD: This app was made in just one page.tsx, next steps are like always;
Performance (server components), Architecture (split into components), UI/UX (visual stuffs), Code (cache functions), New features (ur mind ur ideas), Scalability (less call to server, testing)
Perhaps will work on it if I'm too bored but got a daugther :)