Bryn Flow

⚛️ Build a Weather App with fetch and Navigation

A search screen and a details screen wired together with React Navigation, fetching live weather data and handling loading/error states properly.

Intermediate ⏱️ ~40 minutes 🧰 React Native, fetch, React Navigation
← React Native Track

What You'll Build

A Home screen with a city text field; submitting navigates to a Details screen that fetches and displays that city's current weather, with loading and error states handled explicitly.

Prerequisites

Step by Step

1 Set up the navigator

// App.js
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './HomeScreen';
import DetailsScreen from './DetailsScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Weather' }} />
        <Stack.Screen name="Details" component={DetailsScreen} options={{ title: 'Forecast' }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

2 Build the Home screen

Passing the city as a route param keeps the Details screen decoupled from Home — it doesn't need to know how the city was chosen.

// HomeScreen.js
import React, { useState } from 'react';
import { View, TextInput, Pressable, Text, StyleSheet } from 'react-native';

export default function HomeScreen({ navigation }) {
  const [city, setCity] = useState('');

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="City name"
        value={city}
        onChangeText={setCity}
      />
      <Pressable
        style={styles.button}
        onPress={() => navigation.navigate('Details', { city })}
        disabled={!city.trim()}
      >
        <Text style={styles.buttonText}>Get Weather</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, justifyContent: 'center' },
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, marginBottom: 12 },
  button: { backgroundColor: '#1c8fb0', borderRadius: 8, padding: 14, alignItems: 'center' },
  buttonText: { color: '#fff', fontWeight: 'bold' },
});

3 Build the Details screen with fetch

// DetailsScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';

const API_KEY = 'YOUR_API_KEY';

export default function DetailsScreen({ route }) {
  const { city } = route.params;
  const [status, setStatus] = useState('loading'); // 'loading' | 'success' | 'error'
  const [weather, setWeather] = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function loadWeather() {
      try {
        const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=metric`;
        const res = await fetch(url);
        if (!res.ok) throw new Error('Request failed');
        const data = await res.json();
        if (!cancelled) {
          setWeather(data);
          setStatus('success');
        }
      } catch (e) {
        if (!cancelled) setStatus('error');
      }
    }

    loadWeather();
    return () => { cancelled = true; }; // avoid setting state after unmount
  }, [city]);

  if (status === 'loading') {
    return <View style={styles.center}><ActivityIndicator size="large" /></View>;
  }

  if (status === 'error') {
    return (
      <View style={styles.center}>
        <Text>⚠️ Couldn't load weather for "{city}". Check the spelling and try again.</Text>
      </View>
    );
  }

  return (
    <View style={styles.center}>
      <Text style={styles.city}>{weather.name}</Text>
      <Text style={styles.temp}>{Math.round(weather.main.temp)}°C</Text>
      <Text>{weather.weather[0]?.description}</Text>
      <Text>Humidity: {weather.main.humidity}%</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  center: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
  city: { fontSize: 24, fontWeight: 'bold' },
  temp: { fontSize: 48, fontWeight: '200', marginVertical: 8 },
});

The cancelled flag prevents a "can't update state on an unmounted component" warning if the user navigates back before the request finishes.

4 Run it

Start the app, type a city on the Home screen, tap "Get Weather" — you should see a spinner, then either the forecast or an error message. Try navigating back and forward again to confirm the fetch re-runs for a new city.

Final Working Code

Three files — App.js, HomeScreen.js, DetailsScreen.js — exactly as shown above form the complete, working app.

What to Try Next

Want more React Native walkthroughs?

Head back to the React Native track for the full topic list, roadmap, and practice quiz.

Back to React Native Track