React.js: fetching the weather data in the useEffect hook

This blog continues my React/TypeScript miniseries and the previous blogs are:

1. Let’s start coding with TypeScript and React.js
2. React.js: A functional component and its state
3. Using the React.js state hook in the weather app

In this blog, I’ll add the useEffect() hook to fetch the weather info in the app that we started in the previous blog, where you learned how to store the city in the state of the App component. But our ultimate goal is finding the weather in the given city by fetching the data from an external server. Using the terminology from functional programming, we need to write a function with side effects. As opposed to pure functions, the functions with side effects use the external data and every invocation may produce different results even if the function arguments remain the same.

In React’s functional components, we’ll be using the useEffect() hook for implementing the functionality with side effects. By default, React automatically invokes the callback function passed to useEffect() after every DOM rendering. Let’s add the following function to the App component from the previous blog:

useEffect(() => console.log("useEffect() was invoked"));

If you run the app with the browser console open, you’ll see the message “useEffect() was invoked” each time when you enter a character in the input field and the UI is refreshed. Every React component goes through a set of lifecycle events, and if you need you code to be executed after the component was added to the DOM or each time it was was re-rendered, the useEffect() is the right place for such a code. But if you want the code in useEffect() to be executed only once after initial rendering, specify an empty array as the second argument:

useEffect(() => console.log("useEffect() was invoked"), []);

The code in this hook will be executed only once, which makes it a good place for performing the initial data fetch right after the component has been rendered.

Let’s assume you live in London and would like to see the weather in London as soon as this app is launched. Start by initializing the city state with “London”:

const [city, setCity] = useState('London');

Now you’d need to write a function that would fetch the data for the specified city. The URL will include the following static parts (replace 12345 with your APPID).

const baseUrl = 'http://api.openweathermap.org/data/2.5/weather?q=';
const suffix = "&units=imperial&appid=12345";

In between, you need to place the name of the city, so complete URL may look like this:

baseUrl + 'London' + suffix

For making Ajax requests, we’ll use the browser’s Fetch API (see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). The function fetch() returns a Promise, and we’ll use the async/await keywords in our method getWeather() that can look like this:

const getWeather = async (city: string) => {
  const response = await fetch(baseUrl + city + suffix); 
  const jsonWeather = await response.json(); 
  console.log(jsonWeather); 
}

NOTE: I prefer using the async/await keywords for asynchronous code, but using promises with chained .then() invocations would also work here.

When you use a standard browser’s fetch() method, getting the data is a two-step process: You get the response first, and then you need to call the json() function on the response object to get to the actual data.

TIP: JavaScript developers often use third-party libraries for handling HTTP requests. One of the most popular ones is a promise-based library called Axios (see https://www.npmjs.com/package/axios).

Now you can use this function for the initial data fetch in the useEffect():

useEffect( () => getWeather(city), []);

If you want the code in useEffect() to be executed selectively only if a specific state variable changed, you can attach the hook to such a state variable. For example, you can specify that the useEffect() has to run only if the city gets updated as follows:

useEffect(() => console.log("useEffect() was invoked"),
                 ['city']);

The current version of the App component is shown below.

import React, { useState, useEffect, ChangeEvent } from 'react';

const baseUrl = 'http://api.openweathermap.org/data/2.5/weather?q=';
const suffix = "&units=imperial&appid=12345";

const App: React.FC = () => {

 const [city, setCity] = useState('London');

 const getWeather = async (city: string) => { 
      const response = await fetch(baseUrl + city + suffix);
      const jsonWeather = await response.json();
      console.log(jsonWeather);
  }

  useEffect( { () => getWeather(city) }, []);  // this line has an issue

 const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    setCity( event.target.value ); 
}

  return (
    <div>
      <form>
        <input type="text" placeholder="Enter city"
               onInput = {handleChange} />
        <button type="submit">Get Weather</button>
        <h2>City: {city}</h2>
      </form>
    </div>
  );
}

export default App;


The second argument of the useEffect() is an empty array, so getWeather() will be invoked only once when the App component is initially rendered.

Note: If you run this app, the browser’s console will show the following warning: React Hook useEffect has a missing dependency: ‘city’. Either include it or remove the dependency array react-hooks/exhaustive-deps. The reason is that inside this hook we use the state variable city, which is a dependency and should be listed in the array. This is not an error and for simplicity I’ll keep this code as is, but you should keep this in mind while designing your hooks.

Tip: For the in-depth coverage of the useEffect() hook, read the article by Dan Abramov “A complete guide to useEffect“.

Run this app with the browser console open, and it’ll print the retrieved JSON with the London weather:

The initial data fetch is complete for the default city, and it looks like a good idea to store the retrieved weather data in the component’s state. Let’s define a new type Weather as shown in listing below for storing the content of the properties name and main marked in the above screenshot.

export interface Weather {
  city: string; 
  humidity: number; 
  pressure: number; 
  temp: number;     
  temp_max: number; 
  temp_min: number; 
}

In the App component, we’ll add a new state variable weather and the function to update it as follows:

const [weather, setWeather] = useState(null);

Note that the hook useState() allows you to use a generic parameter for better type safety. Now we need update the function getWeather() so it saves the retrieved weather and city name in the component’s state as shown below:

async function getWeather(location: string) {
  const response = await fetch(baseUrl + location + suffix);
  if (response.status === 200){
    const jsonWeather = await response.json();
    const cityTemp: Weather = jsonWeather.main; 
    cityTemp.city=jsonWeather.name;  
    setWeather(cityTemp);  
  } else {
      setWeather(null);  
  }
}

This code takes the object jsonWeather.main and the city name from jsonWeather.name, and saves them in the state variable weather.

So far, our function getWeather() was invoked for the initial retrieval of the London weather by the hook useEffect(). The next step is to add the code to invoke getWeather() when the user enters any other city and clicks on the button Get Weather. This button is a part of the form (its type is submit), so we’ll add the event handler to the tag. The function handleSubmit() and the first version of the JSX:

const handleSubmit = (event: FormEvent) => { 
   event.preventDefault();  
   getWeather(city);  
 }

return (
   <div>
     <form onSubmit = {handleSubmit}>  
       <input type="text" placeholder="Enter city"
              onInput = {handleChange} />
       <button type="submit">Get Weather</button>
       <h2>City: {city}</h2>
       {weather &&  <h2>Temperature: {weather.temp}F</h2>}  
     </form>
   </div>
 );

In React, event handlers get instances of SyntheticEvent, which is an enhanced version of browser’s native events (see https://reactjs.org/docs/events.html for details). SyntheticEvent has the same interface as the browser native events (e.g. preventDefault()) but events work identically across all browsers.

To pass the argument to getWeather(city), we didn’t have to find the reference to the field on the UI. The component’s state city was updated as the user typed the name of the city, so the variable city already has the value displayed in the field. The next screenshot was taken after the user entered Miami and clicked on the button Get Weather.

What if the user enters the city that doesn’t exist or not supported by openweathermap.org? The server returns 404 and we should add the appropriate error handling. So far we have the following line to prevent displaying the temperature if the weather state is falsy:

{ weather &&  <h2>Temperature: {weather.temp}F</h2> }

In the next version of this app, we’ll create a type guard to check if the weather for the provided city was received or not. For now, let’s take a breather and recap what we did in this and the previous blogs in the weather app:

1. Applied for the APPID at openweathermap.org
2. Generated a new app and replaced the JSX with a simple
3. Declared the state city using the hook useState()
4. Added the function handleChange() that updated city on each change in the input field
5. Added the hook useEffect() that would be invoked only once on the app startup
6. Ensured that useEffect() invokes the function getWeather() that uses the fetch() API to retrieve the weather in London
7. Declared the state weather to store the retrieved temperature and humidity
8. Added the event handler handleSubmit() to invoke getWeather() after the user entered the city name and clicked on the button Get Weather
9. Modified the function getWeather() to save the retrieved weather in the state weather
10. Displayed the retrieved temperature on the web page under the form

This is all good, but you shouldn’t to program all the app logic in one App component, and in the next blog, we’ll create a separate component to be responsible for displaying the weather data.

The source code of the completed app is here. If you want to learn TypeScript quickly, get our book TypeScript Quickly.

6 thoughts on “React.js: fetching the weather data in the useEffect hook

  1. I’m sorry, but ideologically your useEffect usage is not correct (and ESLint with eslint-plugin-react-hooks will also point you that). You need to fetch weather for initial city only once on component mount (and your empty array as a second parameter sets it up). But inside useEffect body you have dependency from city, which is state dependency (getWeather(city)). It is better to add this dependency to dependencies list (useEffect( { () => getWeather(city) }, [city]);) but it will break all your logic of fetching weather for cities on submit but not while typing. To solve this problem and to have ideologically correct code, I’d use const INITIAL_CITY:string = “London”; outside your functional component scope, and use it both in setting initial state (const [city, setCity] = useState(INITIAL_CITY);) and useEffect (useEffect( { () => getWeather(INITIAL_CITY) }, []);)

    1. Thank you, you’re right, but I wanted to make the explanations as simple as possible. To see this issue you don’t even need the linter and the plugin. Just run this app and the browser’s console will show this message:

      ./src/App.tsx
      Line 17: React Hook useEffect has a missing dependency: ‘city’. Either include it or remove the dependency array react-hooks/exhaustive-deps

      I’ll add a note to the text explaining this.

Leave a comment