Building a Modern Weather App with React Hooks: A Comprehensive Guide

React has taken the web development world by storm since its initial release in 2013. It‘s now the most popular front-end JavaScript library, used by companies like Facebook, Netflix, Airbnb, and many more. According to the 2020 Stack Overflow Developer Survey, React is the second most loved web framework, only behind ASP.NET Core.

So what makes React so great? Some key benefits:

  • It‘s declarative and component-based, making code more predictable and easier to debug
  • It‘s very performant thanks to its virtual DOM implementation and reconciliation algorithm
  • It has a huge ecosystem of libraries and tools
  • It can be used to build web, mobile, and desktop apps

In this guide, we‘ll harness the power of React and its hooks API to build a modern, responsive weather application. We‘ll cover everything from setting up the development environment to deploying the finished app, with plenty of code examples and expert tips along the way.

Prerequisites

To follow along with this guide, you should have a basic understanding of HTML, CSS, and JavaScript. Familiarity with ES6+ syntax and features like arrow functions, destructuring, and template literals will be helpful.

You‘ll also need Node.js and npm installed on your machine. I recommend using the latest LTS version of Node.

Finally, while not strictly required, some experience with React will make this guide easier to follow. If you‘re new to React, I recommend first completing the official tutorial before diving into this project.

Setting up the Project

Let‘s start by creating a new React project using Create React App (CRA). This handy tool sets up a modern web development environment with no configuration required. It includes things like Webpack for module bundling, Babel for JavaScript transpilation, ESLint for linting, and more.

Open up your terminal and navigate to the directory where you want to create your project. Then run:

npx create-react-app weather-app
cd weather-app

This will create a new directory called weather-app with the following structure:

weather-app/
  README.md
  node_modules/
  package.json
  public/
    index.html
    favicon.ico
  src/
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg

The public directory contains the HTML file that will be served to the browser. The src directory contains all of our JavaScript and CSS code.

Open the project in your favorite code editor and run npm start to start the development server. Your default browser should open automatically to http://localhost:3000 and you‘ll see the CRA welcome page.

Project Structure

Before we start writing code, let‘s think about how to structure our project. One of the benefits of React is that it enforces a component-based architecture – we build our UI out of small, reusable components that manage their own state and rendering logic.

A typical React weather app might have the following components:

App
  Header
  SearchBar
  WeatherDisplay
    CurrentWeather
    DailyForecast
    HourlyForecast
  Footer

The App component is the top-level component that manages the overall state and rendering of the app. It would handle things like fetching weather data from an API and passing that data down to child components.

The Header and Footer components are simple, presentational components that display static content like the app title and copyright notice.

The SearchBar component allows the user to input a location to fetch weather data for. It manages its own state (the current search query) and communicates with the parent App component via callback props.

The WeatherDisplay component is responsible for displaying the fetched weather data. It likely wouldn‘t manage any of its own state, instead receiving data via props from the parent App component. It would be composed of several child components:

  • CurrentWeather displays the current weather conditions
  • DailyForecast displays the forecast for the next few days
  • HourlyForecast displays the hour-by-hour forecast for today

Of course, this is just one possible component structure – you may choose to break things down differently depending on the specific features and design of your app. The key is to keep components small, focused, and reusable.

Building the Components

Now that we have a plan, let‘s start building out our components! We‘ll use functional components and hooks for state management and side effects.

The App Component

Update src/App.js with the following code:

import React, { useState } from ‘react‘;
import axios from ‘axios‘;
import SearchBar from ‘./components/SearchBar‘;
import WeatherDisplay from ‘./components/WeatherDisplay‘;
import ‘./App.css‘;

const API_KEY = process.env.REACT_APP_API_KEY;
const BASE_URL = ‘https://api.openweathermap.org/data/2.5/weather‘;

function App() {
  const [weatherData, setWeatherData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  async function fetchWeatherData(location) {
    setLoading(true);
    setError(null);

    try {
      const { data } = await axios.get(BASE_URL, {
        params: {
          q: location, 
          units: ‘metric‘,
          appid: API_KEY
        }
      });

      setWeatherData(data);
    } catch (err) {
      setError(err);
    }

    setLoading(false);
  }

  return (
    <div className="App">


      <SearchBar onSearch={fetchWeatherData} />

      {loading && <div>Loading...</div>}
      {error && <div>Error fetching weather data</div>}
      {weatherData && <WeatherDisplay data={weatherData} />}
    </div>
  );
}

export default App;

There‘s a lot going on here, so let‘s break it down:

  • We import the useState hook from React to manage our component state. We‘ll keep track of three pieces of state:
    • weatherData: the fetched weather data for the current location
    • loading: a boolean indicating whether we‘re currently fetching data
    • error: any error that occurred while fetching data
  • We define a fetchWeatherData async function that takes a location string, fetches the corresponding weather data from the OpenWeatherMap API, and updates the component state accordingly. It uses the axios library for making the HTTP request.
  • In the component JSX, we render a heading, the SearchBar component (passing fetchWeatherData as a callback prop), and conditionally render a loading message, error message, or the WeatherDisplay component based on the current state.

Note that we‘re using environment variables to store our API key separately from our code. Create a .env file in the project root with your OpenWeatherMap API key:

REACT_APP_API_KEY=your_api_key_here

Be sure to add .env to your .gitignore file so you don‘t accidentally commit your key.

The SearchBar Component

Create a new file src/components/SearchBar.js with the following code:

import React, { useState } from ‘react‘;

function SearchBar({ onSearch }) {
  const [location, setLocation] = useState(‘‘);

  function handleChange(e) {
    setLocation(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault();
    onSearch(location);
    setLocation(‘‘);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        placeholder="Enter location"
        value={location}
        onChange={handleChange}
      />
      <button type="submit">Search</button>
    </form>
  );
}

export default SearchBar;

This component manages its own state (location) and renders a simple form with a text input and submit button. When the form is submitted, it calls the onSearch callback prop with the current location value.

The WeatherDisplay Component

Create a new file src/components/WeatherDisplay.js with the following code:

import React from ‘react‘;
import CurrentWeather from ‘./CurrentWeather‘;
import DailyForecast from ‘./DailyForecast‘;
import HourlyForecast from ‘./HourlyForecast‘;

function WeatherDisplay({ data }) {
  const { name, main, weather, wind } = data;

  return (
    <div className="WeatherDisplay">
      <h2>{name}</h2>
      <CurrentWeather main={main} weather={weather[0]} wind={wind} />
      <DailyForecast lat={data.coord.lat} lon={data.coord.lon} />
      <HourlyForecast lat={data.coord.lat} lon={data.coord.lon} />
    </div>
  );
}

export default WeatherDisplay;

This component receives the fetched weather data via props and renders the city name and three child components:

  • CurrentWeather displays the current temperature, humidity, pressure, wind speed, and weather icon
  • DailyForecast displays the daily forecast for the next 5 days
  • HourlyForecast displays the hour-by-hour forecast for the next 12 hours

We pass the relevant data to each child component via props. For the forecast components, we pass the latitude and longitude of the city so they can fetch the forecast data independently.

I‘ll leave the implementation of the child components as an exercise for the reader, but they would follow a similar pattern of receiving data via props and rendering the appropriate HTML and CSS.

Styling the App

While the focus of this guide is on React and JavaScript, let‘s add some basic styles to make our app look a bit nicer. Create a new file src/App.css with the following code:

body {
  margin: 0;
  font-family: sans-serif;
}

.App {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.WeatherDisplay {
  margin-top: 20px;
}

input, button {
  padding: 10px;
  font-size: 16px;
}

button {
  background-color: #0074d9;
  color: white;
  border: none;
  margin-left: 10px;
}

You can of course customize and expand upon these styles to match your desired design.

Adding Advanced Features

Now that we have a basic working app, let‘s think about some more advanced features we could add:

  • User preferences: Allow users to set their preferred units (Celsius/Fahrenheit) and save their most recent search location. We could use the localStorage API for client-side persistence.
  • Autocomplete search: Implement an autocomplete feature for the search bar using the OpenWeatherMap Geocoding API. This would help users find locations more quickly and easily.
  • Charts and graphs: Use a charting library like Chart.js to display weather data over time in a more visual way. For example, we could show a line graph of temperature over the next 24 hours.
  • Maps: Integrate with a mapping library like Leaflet to display a map of the current location and allow users to explore weather in different areas.
  • Push notifications: If building a mobile app with React Native or PWA, we could allow users to opt-in to push notifications for severe weather alerts in their area.

The great thing about React is that it‘s easy to add and remove features as needed. The component-based architecture and declarative API make it simple to reason about our code and make changes without breaking things.

Testing and Deployment

Before deploying our app to production, we should add some tests to verify that it works as expected. Jest, React‘s built-in testing framework, makes it easy to write unit tests for our components.

For example, to test the SearchBar component, we could write the following test in src/components/SearchBar.test.js:

import React from ‘react‘;
import { render, fireEvent } from ‘@testing-library/react‘;
import SearchBar from ‘./SearchBar‘;

test(‘calls onSearch with correct location when form is submitted‘, () => {
  const onSearch = jest.fn();
  const { getByPlaceholderText, getByText } = render(<SearchBar onSearch={onSearch} />);

  const input = getByPlaceholderText(‘Enter location‘);
  const submitButton = getByText(‘Search‘);

  fireEvent.change(input, { target: { value: ‘New York‘ } });
  fireEvent.click(submitButton);

  expect(onSearch).toHaveBeenCalledTimes(1);
  expect(onSearch).toHaveBeenCalledWith(‘New York‘);
});

This test renders the SearchBar component with a mock onSearch callback, simulates a user typing "New York" into the input and clicking the submit button, and verifies that onSearch was called with the correct location argument.

We could write similar tests for our other components to verify that they render and behave correctly with different props.

To run our tests, we can simply run npm test from the terminal. Jest will look for any files ending in .test.js and run them.

Once we‘re confident that our app is working well, we‘re ready to deploy to production. There are many options for deploying React apps, but one of the simplest is Netlify:

  1. Push your code to a Git repository on GitHub, GitLab, or Bitbucket
  2. Log in to Netlify and click "New site from Git"
  3. Connect your Git provider and select your repository
  4. Configure your build settings:
    • Base directory: ./
    • Build command: npm run build
    • Publish directory: build/
  5. Click "Deploy site"

Netlify will automatically build and deploy your app every time you push changes to your Git repository. You‘ll get a unique URL where you can access your live app.

Be sure to configure any necessary environment variables (like your API key) in the Netlify UI under "Site settings" > "Build & deploy" > "Environment".

Conclusion

Congratulations, you now have a fully-functional, modern weather app built with React and hooks! We covered a lot of ground in this guide, including:

  • Setting up a new React project with Create React App
  • Managing state and side effects with the useState and useEffect hooks
  • Making HTTP requests with Axios
  • Building reusable functional components
  • Adding basic styles with CSS
  • Writing unit tests with Jest
  • Deploying to production with Netlify

Of course, there‘s always more to learn and improve upon. Here are some ideas for further exploration:

  • Dive deeper into React‘s more advanced hooks like useContext, useReducer, and useRef
  • Explore other state management solutions like Redux or MobX
  • Learn about performance optimization techniques like memoization and code splitting
  • Investigate static typing with TypeScript or Flow
  • Experiment with server-side rendering and JAMstack architectures

The React ecosystem is constantly evolving, with new features, tools, and best practices emerging all the time. As a developer, it‘s important to stay curious and keep learning.

I hope this guide has given you a solid foundation in building modern web apps with React and hooks. Remember to break down problems into smaller pieces, keep components focused and reusable, and always strive for clean, readable code.

Now go forth and build something amazing!

Similar Posts