Building a Weather App: Exploring React and Redux

ยท

5 min read

Building a Weather App:
Exploring React and Redux

Introduction

In the world of web development, creating a practical application that solves real-world problems is an exciting challenge. In this blog post, I want to share my journey of building a weather report app using React and Redux. This project allowed me to dive deep into front-end technologies and learn valuable lessons along the way.

Project Overview

The weather report app I developed provides users with real-time weather information for a given city. It displays the current temperature, humidity, and a brief description of the weather conditions. Users can search for any city and instantly receive the latest weather data.

Technologies Used

I decided to build the weather report app using React and Redux due to their powerful combination for managing state and building interactive user interfaces. Additionally, I utilized the Redux Thunk middleware for handling asynchronous actions, which was essential for fetching weather data from an API. For styling I used Sass for getting much more clean styling. I utilized OpenweatherAPI for data fetching.

Challenges Faced

Throughout the development process, I encountered several challenges. One notable of them was effectively managing asynchronous API calls using Redux Thunk. Learning to structure my actions and reducers to handle loading, success, and error states was a crucial step. At the beginning of the time, it was very hard for me to understand how I have to create a project structure, but after watching 2-3 YouTube videos I clarified it.

Some screenshots from a file structure

Redux part file structure:

redux file structure

React components:

Styling part:

Development Process

I followed a component-based architecture in React, breaking down the user interface into reusable components. This allowed me to keep my codebase organized and maintainable. In this part, I followed some important steps:

  • Created Redux action types (e.g., FETCH_WEATHER_REQUEST, FETCH_WEATHER_SUCCESS, FETCH_WEATHER_FAILURE).
    Implemented action creators for these actions.

  • Created a Redux Thunk action to fetch weather data.
    Used async/await function to make API requests to OpenWeatherMap.

  • In the search bar component, create an input field where users can type a location. Added a button that users will click to initiate the weather search.

  • Attached is an event handler to the search button.
    When the button is clicked, dispatch the Redux Thunk action to fetch weather data using the input value.

  • Updated the weather display component to show fetched weather information.
    Displayed temperature, weather description, and an icon based on the condition.

  • Handled API request errors and display error messages to users. When a request gives an error, an error message is shown in the component.

  • Handle API request loading and display loading messages to users. In this part I used ready HTML/CSS written Loading animation.

    I utilized this loader: UI Verse Loader

App UI

Loading screen:

Success screen:

Error screen:

Key Takeaways

  1. Redux State Management: Implementing Redux for state management taught me the importance of centralizing and controlling application data. It also provided a clear way to handle asynchronous operations while maintaining a predictable state.

  2. Asynchronous Actions: Working with Redux Thunk gave me insight into managing asynchronous actions. This was particularly valuable when fetching weather data from an external API.

  3. Code Structure: Organizing the project into modular components not only enhanced the readability of the code but also simplified debugging and maintenance.

Code Highlights

Main store file, index.js:

//main store file: index.js

import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "./reducers";

import thunkMiddleware from "redux-thunk";

const env = "developement";
const composeEnhancers =
  env === "developement"
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
    : compose;

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunkMiddleware))
);

export default store;

Reducers part:

//Reducers part:
const initialState = {
  data: [],
  loading: true,
  error: false,
  errorMessage: null,
  success: false,
};
export const weatherReducer = (state = initialState, action) => {
  switch (action.type) {
    case "FETCH_WEATHER_REQUEST":
      return {
        ...state,
        success: false,
        loading: true,
        error: false,
      };

    case "FETCH_WEATHER_SUCCESS":
      return {
        ...state,
        loading: false,
        data: action.payload,
        error: false,
        success: true,
      };

    case "FETCH_WEATHER_FAILURE":
      return {
        ...state,
        error: true,
        data: [],
        loading: false,
        errorMessage: action.payload,
        success: false,
      };

    default:
      return state;
  }
};

//Instead of one reducer, I created root reducer:
import { combineReducers } from "redux";
import { weatherReducer } from "./weather";

const rootReducer = combineReducers({
  weather: weatherReducer,
});

export default rootReducer;

Actions part:

const FETCH_WEATHER_REQUEST = "FETCH_WEATHER_REQUEST";
const FETCH_WEATHER_SUCCESS = "FETCH_WEATHER_SUCCESS";
const FETCH_WEATHER_FAILURE = "FETCH_WEATHER_FAILURE";

import API_KEY from "../../apikey";

export const fetchPosts = (location) => {
  return async (dispatch) => {
    dispatch({
      type: FETCH_WEATHER_REQUEST,
    });

    try {
      const response = await fetch(
        `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&q=${location}&units=metric`
      );
      const data = await response.json();
      // console.log(data);
      dispatch({
        type: FETCH_WEATHER_SUCCESS,
        payload: data,
      });

      if (data.cod !== 200) {
        dispatch({
          type: FETCH_WEATHER_FAILURE,
          payload: data.message,
        });
      }
    } catch (error) {
      dispatch({
        type: FETCH_WEATHER_FAILURE,
      });
    }
  };
};

Search.js file:

import React, { useState } from "react";
import { fetchPosts } from "../store/actions/weatherActions";
import { useDispatch } from "react-redux";

export default function Search() {
  const [inputVal, setInputVal] = useState("Baku");
  const dispatch = useDispatch();

  const sendRequest = () => {
    dispatch(fetchPosts(inputVal));
  };

  return (
    <div className="search-box">
      <input
        className="search-box--input"
        type="text"
        placeholder="search "
        value={inputVal}
        onChange={(e) => {
          setInputVal(e.target.value);
        }}
      />
      <button className="search-box--btn" onClick={sendRequest}>
        Search
      </button>
    </div>
  );
}

Lessons learned

Reflecting on this project, I realized the significance of planning before diving into coding. Creating a clear roadmap and understanding the flow of data in Redux proved essential. I also learned to embrace challenges as learning opportunities, which ultimately led to a more robust application

Conclusion

Building the weather report app was an enriching experience that deepened my understanding of React, Redux, and asynchronous programming. The satisfaction of seeing a functional application come to life from scratch is truly rewarding. If you're looking to expand your front-end development skills, consider taking on a project that piques your interest and challenges you to grow.

Connect with me on GitHub and feel free to explore the live demo of the Weather Report App!

ย