Skip to content

Exercise 06 : Weather Forecast

Purpose and learning process

In this exercise you will learn how to display a weather forecast in React Native CLI app.

  • using and rendering build-in components
  • using and rendering 3th party UI components
  • understand state and props
  • event handling
  • passing data between components
  • using styles
  • load and parse JSON data

You will create a Weather Forecast application with React Native CLI. Weather Forecast will be loaded with Axios from the OpenWeather. Application UI will be created with React Native Element components. Created project is tested with a emulator and a real device. To test with emulators you will need to install Android Studio or Xcode.

Example screenshots

Application will load weather forecast data from OpenWeather and display weather forecast with Card components. A new city can be added from the application options menu.

Weather 10 Weather 11 Weather 12

Example Video

Example video: https://youtu.be/f5cHD1nhWsQ. (old NativeBase version)

Weather Forecast data

Find a free weather forecast provider, which offers XML or JSON data. You can use for example OpenWeather. Get your own API key, you will need it later in this exercise.

Note

Open Weather will/may ask you to give credit card information now. You can use any other weather API if you want. For example following Weather by API-Ninjas.

UI components

React Native Elements

React Native elements are cross-platform UI toolkit for Android, iOS and web. Built completely in JavaScript. Starting your React Native app has never been easier. Supports Expo too! Easily style any of our components just the way you want. 100% built by the community. We're here because we love open source. Find library here: React Native Elements or from here https://www.npmjs.com/package/react-native-elements.

NativeBase

NativeBase is a free and open source UI component library for React Native to build native mobile apps for iOS and Android platforms. You can find NativeBase website here: NativeBase.

Ant Design Mobile RN

A UI component library based on React Native: Ant Design Mobile.

Axios

In this exercise you can load JSON data using Axios.

App requirements

Your app must meet the following requirements:

  • Create a React Native CLI app
  • Use Axios to load Weather Forecast data
  • App displays current weather forecast
  • Cities can be added and removed
  • UI is created with React Native Elements or NativeBase or some other 3th party UI library for React Native
  • Application saves cities data, so cities are available when app is launched

Create project and install npms

Create WeatherApp React Native CLI app and install needed axios packages.

1
2
3
4
5
6
npx react-native init WeatherApp
npm install axios
npm install axios-hooks@4.0.0
npm install react-native-elements
npm install react-native-safe-area-context
npm install react-native-vector-icons

Note

This exercise material has been created with React Native Elements 3.4.2 version and React Native Vector Icons 10.0.3 version. Read React Native Vector Icons documentation carefully. You need to modify your project Android or iOS files to get icons working.

Create UI

React Native Elements is made from effective building blocks referred to as components. The Components are constructed in pure React Native platform along with some JavaScript functionality with rich set of customisable properties. These components allow you to quickly build the perfect interface.

Read documentation and add a Header component to your app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import type {Node} from 'react';
import { SafeAreaView } from 'react-native';

import { Header } from 'react-native-elements';
import Icon from 'react-native-vector-icons/FontAwesome';

const App = () => {
  return (
    <SafeAreaView>
     <Header
      centerComponent={{ text: 'Weather App', style: { color: '#fff' } }}
      rightComponent={{ icon: 'add', color: '#fff' }}
    />
    </SafeAreaView>
  );
};

export default App;

Weather 4

Add a new city

Create UI where user can add/give a new city name. Here you can use (for example) React-Native dialog, which display a dialog - or create own UI for that purpose.

Weather 5

Install React-Native dialog npm:

1
npm install react-native-dialog

Add a Dialog for rendering (before closing SafeAreaView component). Input component will be used to get a new city name. onChangeText event will use React Hooks to change cityName state (look codes later).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<SafeAreaView>
  <Header
    centerComponent={{ text: 'Weather App', style: { color: '#fff' } }}
    rightComponent={{ icon: 'add', color: '#fff', onPress: openDialog }}
  />
  <Dialog.Container visible={modalVisible}>
    <Dialog.Title>Add a new city</Dialog.Title>
    <View>
      <Input
        onChangeText={ (text) => setCityName(text)}
        placeholder='Type cityname here'
      />
    </View>
    <Dialog.Button label="Cancel" onPress={cancelCity} />
    <Dialog.Button label="Add" onPress={addCity} />
  </Dialog.Container>
</SafeAreaView>

Tip

Remember import react-native-dialog and needed UI components from react-native-elements.

A dialog should be opened, when add Button component is pressed in header. Here you can use for example React Hooks to change dialog visible/invisible state.

Note

Hooks are new in React and you should give time for learning it.

Add React Hooks for modalVisible, cityName and citiesstates.

1
2
3
const [modalVisible, setModalVisible] = useState(false); 
const [cityName, setCityName] = useState(""); 
const [cities, setCities] = useState([]);

Add a onPress event handling to Header component rightComponent (+ button).

1
rightComponent={{ icon: 'add', color: '#fff', onPress: openDialog }}

Add openDialog function, which will modify modalVisible state (dialog is visible/invisible).

1
2
3
const openDialog = () => {
  setModalVisible(true);
}

Dialog's Add button will call addCity function, which will add city to cities array and hides a dialog.

1
2
3
4
const addCity = () => {
  setCities( [...cities,{id:Math.random(), name:cityName}]);
  setModalVisible(false);
}

Note

Now Math.random() is used to generate unique id. You should make this better to generate unique id's.

Code cancelCity function just to set modalVisible to false with setModalVisible function.

Cities array can be rendered after Header component. Use map function to loop through cities array and generate Text component to show a city name.

1
2
3
{cities.map(city => (
  <Text key={city.id}>{city.name}</Text>
))}

Test - Add a new city

Save and test your app. It should display city names now.

Weather 6 Weather 7 Weather 8

Use own made component with cities

The purpose of this exercise is to use Card component to show city weather forecast. Check and learn Card component.

Create own WeatherForecast component. Component will get city name via params.

1
2
3
4
5
6
7
8
const WeatherForecast = (params) => {
  const city = params.city;
  return (
    <Card>
      <Card.Title>{city.name}</Card.Title>
    </Card>
  );
}

Use above WeatherForecast component in main App.

1
2
3
4
5
<ScrollView>
  {cities.map(city => (
    <WeatherForecast key={city.id} city={city} />
  ))}
</ScrollView>

Test - City with Card component

Save and test your app. It should display city name inside a Card component.

Weather 9

Load a weather forecast

You can access current weather data for any location on Earth including over 200,000 cities with current weather data @ OpenWeather. Check and learn the API calls to get weather forecast by city name.

Weather forecast can be now loaded and displayed inside a WeatherForecast component. Use React hooks for axios to load weather forecast.

Install npm (if not installed already at the beginning)

1
npm install axios-hooks

Import axios-hooks and load weather forecast inside a WeatherForecast component.

1
import useAxios from 'axios-hooks';

WeatherForecast component will load a weather forecast from city, which is passed here with parameters. Data will be loaded with useAxios hooks. This will nicely told lifecycle of data loading: loading and error states. Component will display correct text in Card component.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const WeatherForecast = (params) => {
  const city = params.city;
  const API_KEY = 'YOUR_API_KEY_HERE';
  const URL = 'https://api.openweathermap.org/data/2.5/weather?q=';

  const [{ data, loading, error }, refetch] = useAxios(
    URL+city.name+'&appid='+API_KEY+'&units=metric'
  )

  if (loading) return (
    <Card>
      <Card.Title>Loading....</Card.Title>
    </Card>
  )
  if (error) return (
    <Card>
      <Card.Title>Error loading weather forecast!</Card.Title>
    </Card>
  )

  // just for testing
  console.log(data);

  return (
    <Card>
      <Card.Title>{city.name}</Card.Title>
    </Card>
  );
}

Note

Remember change your own API key.

Test - Loading status and data

Save and test your app. Check your Metro builder's log data, it should display weather forecast data in JSON format.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "base": "stations", 
  "clouds": {"all": 75}, 
  "cod": 200, 
  "coord": {"lat": 61.5, "lon": 23.75}, 
  "dt": 1582045660, 
  "id": 634964, 
  "main": {"feels_like": 270.86, "humidity": 75, "pressure": 988, "temp": 277.8, "temp_max": 278.15, "temp_min": 277.15}, 
  "name": "Tampere", 
  ...

Learn returned JSON structure or read API carefully.

Show a weather forecast

Select some of the weather forecast data and show it inside a WeatherForecast component.

For example - main and temp. Be innovative with UI!

1
2
<Text>Main: {data.weather[0].main}</Text>
<Text>Temp: {data.main.temp} °C</Text>

Test - UI should be ready

Save and test your app. It should display weather forecast inside a Card component.

Weather 10 Weather 11 Weather 12

Refresh and delete weather forecast functionality

Add a refresh "Button" to your app and refresh weather forecast.

Weather 13

You can use already defined React refetch hooks. It will automatically use Axios to fetch a new forecast.

1
2
3
const refreshForecast = () => {
  refetch();
}

Remove forecast

Add a remove "Button" to your app and delete city from cities state. Look above image.

You should add deleteCity params to WeatherForecast component rendering in main App.

1
2
3
{cities.map(city => (
    <WeatherForecast key={city.id} city={city.name} deleteCity={deleteCity} />
))}

Add and call below deleteCity function from your "Button" in WeatherForecast component. City will be send to main App's deleteCity function.

1
2
3
const deleteCity = () => {
  params.deleteCity(city.id);
}

Add below deleteCity function to main App. Array's filter function will be used to filter out city with exact id and a new state for cities will be set with setCities hook.

1
2
3
4
const deleteCity = (deleteCity) => {
  let filteredArray = cities.filter(city => city.id !== deleteCity.id);
  setCities(filteredArray);
}

Test - delete city

Save and test your app. It should work with add/remove city and refresh weather forecast.

Weather 15 Weather 16

Save and load cities to/from local storage

A final step is to save cities to device's local storage. You can use React Native Async Storage, which is a an asynchronous, unencrypted, persistent, key-value storage system for React Native.

Read documentation and install it to your project.

You should stringify cities, before save it to local storage.

1
2
3
4
5
6
7
8
const storeData = async () => {
  try {
    await AsyncStorage.setItem('@cities', JSON.stringify(cities));
  } catch (e) {
    // saving error
    console.log("Cities saving error!");
  }
}

And remember parse it back to array and use setCities hook to set cities.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const getData = async () => {
  try {
    const value = await AsyncStorage.getItem('@cities')
    if(value !== null) {
      setCities(JSON.parse(value));
    }
  } catch(e) {
    console.log("Cities loading error!");
  }
}

Above functions should be called, when cities need to be saved and loaded to/from local storage.

React Native's useEffect hook can be used here in both save and read data. Check documentation here: Using the Effect Hook.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

Now saved cities are loaded only once, when app starts, so pass [] empty array in second argument and call above getData to load saved cities from local storage.

1
2
3
4
// load cities when app starts
useEffect(() => {
  getData();
},[]);  

Modified cities can be detected with useEffect also. Pass [cities] array in second argument. storeData will be called every time, when cities state changes (cities are added or removed).

1
2
3
4
// save cities if cities state changes
useEffect(() => {
  storeData();
},[cities]); 

Push to GitLab

Test your application in emulator, take screenshots (add those to your project folder) and commit/push your mobile-exercises repository back to JAMKIT/GitLab. Remember move your exercise/issue ticket from Doing to In Review in Issues Board and write your learning comments to issue comments.