React - Practical Projects

[Project 3] Weather check app - open API, Postman, Context API, .env, Swiper, Recharts, Material-ui, React-icons

olivia_yj 2023. 3. 12. 06:34

 

 

Open Weather Map API

I will use the free option to build an app.

 

Before starting to build, I will check this API first with Postman

 

 

 

Put API key and other keys then we can get the values.

 

Now make the project and components first to show the API data later.

 

import React, { createContext } from "react";

export const WeatherContext = createContext({});

export default function WeatherProvider(children) {
  return (
    <WeatherContext.Provider value={{}}>{children}</WeatherContext.Provider>
  );
}

First, write the basic structure for the project.

 

This code is for creating a context and a provider component in a React application that can be used to share weather-related data and functionality across the application.

Here's what each line of the code does:

  • The first line imports the React library and the createContext function from React.
  • The second line creates a new context object named WeatherContext using the createContext() function. This context object is used to pass data down to child components without the need to explicitly pass it through props at every level of the component hierarchy.
  • The third line exports the WeatherContext object for use in other parts of the application.
  • The fourth line defines a new function component named WeatherProvider that takes a single prop named children. The children prop is a special prop in React that refers to the child components wrapped inside the WeatherProvider component.
  • Inside the WeatherProvider component, there is a single return statement that returns a WeatherContext.Provider component. This component wraps the child components passed in as props (using the {children} syntax).
  • The value prop of the WeatherContext.Provider is set to an empty object for now, but this is where weather data or functionality could be added later on. By setting an initial empty object, it ensures that the provider is ready to accept any value provided through the value prop.

Overall, this code sets up a context object and a provider component that can be used to share weather data or functionality throughout the app.

 

import React, { createContext, useEffect, useState } from "react";

export const WeatherContext = createContext({});

export default function WeatherProvider({ children }) {
  const [weatherInfo, setWeatherInfo] = useState({});

  const getWeatherInfo = async () => {
    try {
      const currentWeatherInfoAPI = `https://api.openweathermap.org/data/2.5/weather?appid=${process.env.REACT_APP_WEATHER_API}&q=malmö&units=metric`;
      const currentWeatherInfo = await fetch(currentWeatherInfoAPI);
      const {
        name,
        coord: { lat, lon },
        main: { temp, humidity, feels_like, temp_min, temp_max },
        sys: { sunset, sunrise },
        weather: [{ main: weatherState }],
        wind: { speed, deg },
      } = await currentWeatherInfo.json();
      const fivedaysWeatherInfoAPI = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&exclude=current,minutely,daily&appid=${process.env.REACT_APP_WEATHER_API}&units=metric`;
      const fivedaysWeatherInfo = await fetch(fivedaysWeatherInfoAPI);
      const { fivedays } = await fivedaysWeatherInfo.json();
      setWeatherInfo({
        name,
        temp,
        humidity,
        weatherState,
        feels_like,
        speed,
        deg,
        fivedays,
        sunset,
        sunrise,
        temp_max,
        temp_min,
      });
    } catch (error) {
      console.error(error);
    }
  };
  useEffect(() => {
    getWeatherInfo();
  }, []);

  return (
    <WeatherContext.Provider value={{ ...weatherInfo }}>
      {children}
    </WeatherContext.Provider>
  );
}

This code exports a WeatherProvider component and a WeatherContext context for sharing weather information across multiple components in a React application.

The WeatherProvider component is a functional component that accepts children as props. It uses the useState hook to initialize a weatherInfo state object with an empty object as the initial value. It also defines an async function called getWeatherInfo that makes two API requests to OpenWeatherMap API to fetch the current weather information for "Malmö", Sweden, and the next five days' weather forecast based on the latitude and longitude coordinates returned by the first API call. If the API calls are successful, it extracts the relevant data from the response using destructuring assignment and updates the weatherInfo state object using the setWeatherInfo function.

The useEffect hook is used to call the getWeatherInfo function only once when the component is mounted by passing an empty array as its second argument. This ensures that the API requests are not made repeatedly.

The WeatherContext is created using the createContext function from React and exported for use in other components. The WeatherProvider component wraps its children with the WeatherContext.Provider component, passing the weatherInfo state object as its value using the spread operator to spread its properties. This makes the weatherInfo state object available to all child components that consume the WeatherContext.

Overall, this code sets up a WeatherProvider component that fetches and stores weather information in its state, and provides that information to its child components via the WeatherContext.

 

import React from "react";
import {
  WiDayCloudy,
  WiDayRain,
  WiDaySunny,
  WiDust,
  WiDaySprinkle,
  WiDayThunderstorm,
  WiDaySnow,
  WiNa,
} from "react-icons/wi";
function CurrentWeatherIcon({ weatherState, ...props }) {
  switch (weatherState) {
    case "Thunderstorm":
      return <WiDayThunderstorm {...props} />;
    case "Snow":
      return <WiDaySnow {...props} />;
    case "Clouds":
      return <WiDayCloudy {...props} />;
    case "Clear":
      return <WiDaySunny {...props} />;
    case "Haze":
      return <WiDust {...props} />;
    case "Mist":
      return <WiDust {...props} />;
    case "Smoke":
      return <WiDust {...props} />;
    case "Dust":
      return <WiDust {...props} />;
    case "Fog":
      return <WiDust {...props} />;
    case "Sand":
      return <WiDust {...props} />;
    case "Ash":
      return <WiDust {...props} />;
    case "Squall":
      return <WiDust {...props} />;
    case "Tornado":
      return <WiDust {...props} />;
    case "Rain":
      return <WiDayRain {...props} />;
    case "Drizzle":
      return <WiDaySprinkle {...props} />;
    default:
      return <WiNa {...props} />;
  }
}

export default CurrentWeatherIcon;

This code defines a React component called CurrentWeatherIcon. The purpose of this component is to display an icon representing the current weather conditions, based on the weatherState prop that is passed to it.

The component imports a number of weather icons from the react-icons/wi library, which are then used in a switch statement to determine which icon to display based on the weatherState prop. For example, if weatherState is "Thunderstorm", the component returns the <WiDayThunderstorm> icon, and if weatherState is "Rain", the component returns the <WiDayRain> icon.

If the weatherState prop does not match any of the cases in the switch statement, the component returns a <WiNa> icon, which represents "not available" or "unknown".

The ...props syntax in the component's function signature allows any additional props passed to the component to be spread into the rendered icon element. This allows the component to be easily customized with additional props such as className, size, or color.

Finally, the component is exported as the default export of the module, making it available for use in other parts of the application.

 

import React, { useContext } from "react";
import { WeatherContext } from "../weatherProvider/WeatherProvider";
import { WiHumidity, WiStrongWind, WiSunrise, WiSunset } from "react-icons/wi";

function WindDirectionText({ degree = 0 }) {
  switch (true) {
    case (337.5 <= degree && degree <= 360) || (0 <= degree && degree < 22.5):
      return "north wind";
    case 22.5 <= degree && degree < 67.5:
      return "north east wind";
    case 67.5 <= degree && degree < 112.5:
      return "east wind";
    case 112.5 <= degree && degree < 157.5:
      return "south east wind";
    case 157.5 <= degree && degree < 202.5:
      return "south wind";
    case 202.5 <= degree && degree < 247.5:
      return "south west wind";
    case 247.5 <= degree && degree < 292.5:
      return "west wind";
    case 292.5 <= degree && degree < 337.5:
      return "north west wind";
    default:
      return "";
  }
}

function ExtraInfo() {
  const { humidity, speed, deg, sunset, sunrise } = useContext(WeatherContext);
  return (
    <div className="extra-info">
      <div className="extra-info-item">
        <WiSunrise style={{ fontSize: "50px", color: "#ff7500" }} />
        <p className="extra-info-text">
          {new Date(sunrise * 1000).toLocaleString("en-US", {
            hour: "numeric",
            minute: "numeric",
            hour12: true,
          })}{" "}
          <br />
          Sunrise
        </p>
      </div>
      <div className="extra-info-item">
        <WiSunset style={{ fontSize: "50px", color: "#ff7500" }} />
        <p className="extra-info-text">
          {new Date(sunset * 1000).toLocaleString("en-US", {
            hour: "numeric",
            minute: "numeric",
            hour12: true,
          })}
          <br />
          Sunset
        </p>
      </div>
      <div className="extra-info-item">
        <WiHumidity style={{ fontSize: "50px", color: "#0095ff" }} />
        <p className="extra-info-text">
          {`${humidity}%`}
          <br />
          Humidity
        </p>
      </div>

      <div className="extra-info-item">
        <WiStrongWind style={{ fontSize: "50px", color: "#2bc7ad" }} />
        <p className="extra-info-text">
          {`${speed}m/s`} (<WindDirectionText degree={deg} />)
          <br />
          Wind
        </p>
      </div>
    </div>
  );
}

export default ExtraInfo;

This is a React component named ExtraInfo which is importing useContext and WeatherContext from a WeatherProvider module, and also importing some weather-related icons from the react-icons library.

The ExtraInfo component is rendering some weather-related information including the sunrise and sunset time, humidity percentage, wind speed, and wind direction. The values of these pieces of information are retrieved from the WeatherContext using useContext.

The WindDirectionText is a helper function that takes a degree value as input and returns a string indicating the wind direction based on the degree value. The degree value is compared to predefined ranges to determine the direction.

The ExtraInfo component is using the WindDirectionText function to display the wind direction with the wind speed. The component also uses the imported icons to visually represent the information displayed.

 

import { Box, Tab, Tabs } from "@mui/material";
import React, { useState } from "react";
import HumidityGraph from "../humidityGraph/HumidityGraph";
import WeatherGraph from "../weatherGraph/WeatherGraph";
import "swiper/css";
import "swiper/css/navigation";
import WindGraph from "../windGraph/WindGraph";

function TabPanel({ children, value, index }) {
  return (
    <div hidden={value !== index}>
      {value === index && <Box>{children}</Box>}
    </div>
  );
}

function WeatherTab() {
  const [value, setValue] = useState(0);
  const handleChange = (event, newValue) => {
    setValue(newValue);
  };
  return (
    <Box sx={{ width: "100%" }}>
      <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
        <Tabs value={value} onChange={handleChange} variant="fullWidth">
          <Tab label="Weather" />
          <Tab label="Humidity" />
          <Tab label="Wind" />
        </Tabs>
      </Box>
      <TabPanel value={value} index={0}>
        <WeatherGraph />
      </TabPanel>
      <TabPanel value={value} index={1}>
        <HumidityGraph />
      </TabPanel>
      <TabPanel value={value} index={2}>
        <WindGraph />
      </TabPanel>
    </Box>
  );
}

export default WeatherTab;

This code exports a React component called WeatherTab which displays a set of tabs that allow the user to switch between different weather-related graphs: WeatherGraph, HumidityGraph, and WindGraph.

The code imports several components from external dependencies:

  • Box, Tab, and Tabs from the @mui/material library, which provides pre-built React components that follow the Material Design guidelines.
  • React and useState from the react library, which are used for building React components and managing state.
  • HumidityGraph, WeatherGraph, and WindGraph from three separate files in the project directory, which contain the logic for generating the corresponding graphs.

The TabPanel function is a helper function that takes three arguments:

  • children, which represents the content that should be displayed in the tab panel.
  • value, which is the index of the currently selected tab.
  • index, which is the index of the tab panel being rendered.

The TabPanel function returns a div element that is hidden if the value prop is not equal to the index prop. If the value prop is equal to the index prop, the children prop is displayed within a Box component.

The WeatherTab function is the main component that is exported. It uses the useState hook to define a state variable called value, which is initially set to 0. The value state variable is used to keep track of the currently selected tab.

The handleChange function is a callback function that is called whenever the user clicks on a different tab. It updates the value state variable to reflect the newly selected tab.

The return statement in WeatherTab renders a Box component that contains two child components:

  • A Tabs component that displays the three available tabs ("Weather", "Humidity", and "Wind"). The currently selected tab is determined by the value state variable, and the onChange prop is set to the handleChange function.
  • Three TabPanel components that each display a different graph based on the currently selected tab. The value prop is set to the value state variable, and the index prop is set to the index of each tab panel (0 for the "Weather" tab, 1 for the "Humidity" tab, and 2 for the "Wind" tab).

 

import React, { useContext } from "react";
import { Bar, BarChart, XAxis } from "recharts";
import { WeatherContext } from "../weatherProvider/WeatherProvider";
import { Navigation } from "swiper";
import { Swiper, SwiperSlide } from "swiper/react";

const formatXAxis = (data) => `${new Date(data * 1000).getHours()}:00`;

const CustomizedLabel = function ({ x, y, value }) {
  return (
    <text x={x + 30} y={y - 2} fontSize="15" textAnchor="middle">
      {value}%
    </text>
  );
};

function BarGraph({ num }) {
  const { list } = useContext(WeatherContext);
  return (
    <BarChart
      width={960}
      height={200}
      data={list
        ?.slice(num * 12, (num + 1) * 12)
        .map(({ dt, main }) => ({ dt, humidity: main.humidity }))}
      margin={{ top: 30, right: 30, left: 30, bottom: 10 }}
    >
      <XAxis dataKey="dt" fontSize={15} tickFormatter={formatXAxis} />
      <Bar
        dataKey="humidity"
        fill="#2C6CFF"
        isAnimationActive={false}
        label={<CustomizedLabel />}
      />
    </BarChart>
  );
}

function HumidityGraph() {
  const slides = [];

  for (let i = 0; i < 2; i++) {
    slides.push(
      <SwiperSlide key={i}>
        <BarGraph num={i} />
      </SwiperSlide>
    );
  }
  return (
    <Swiper navigation={true} modules={[Navigation]}>
      {slides}
    </Swiper>
  );
}

export default HumidityGraph;

This is a React component that displays a graph of humidity levels for a weather forecast. It imports several components from external libraries:

  • react for building the UI with components and state
  • recharts for creating the bar chart
  • swiper for adding a slide/swipe effect to the chart
  • weatherProvider for getting weather data

The WeatherContext is imported from ../weatherProvider/WeatherProvider using the useContext hook. This hook allows the component to access data stored in the context object created by the WeatherProvider component.

The formatXAxis function is a helper function that takes in a timestamp data and returns a formatted time string. It's used as a tickFormatter for the XAxis component of the BarChart.

The CustomizedLabel function is another helper function that creates custom labels for the bars in the BarChart. It takes in the x and y coordinates of the bar and its value and returns a <text> component that displays the value with a % sign.

The BarGraph function takes in a num prop that determines which set of data to display. It maps through the data obtained from the WeatherContext and extracts the humidity and timestamp values. The timestamp value is formatted using the formatXAxis function and used as the dataKey for the XAxis component. The humidity value is used as the dataKey for the Bar component. A customized label is also added to the bars using the CustomizedLabel function.

The HumidityGraph function creates a series of BarGraph components with different num props using a for loop and adds them to a Swiper component. This allows the user to swipe between two sets of data displayed as two separate graphs.

Finally, the HumidityGraph component is exported as the default export of this module.

 

The LineGraph component renders a line chart showing the temperature over time. The temperature data is obtained from the main.temp property of each object in the list array obtained from the WeatherContext. The list array is first sliced to obtain the data for the current 12-hour period, and then mapped to a new array containing only the dt (date/time), main.temp, and weather[0].main (weather condition) properties. The resulting data is then passed to the LineChart component from Recharts, along with various configuration options such as the chart dimensions, margin, and X axis label formatter. The Line component is used to render the line itself, and is configured with a custom dot component to show weather icons at each data point. It also has a LabelList component to display the temperature value at each data point.

The WeatherGraph component renders a swiper component with two LineGraph components as slides, each showing the temperature for a 12-hour period.

 

 

Context API?

Context API is a feature in React that provides a way to share data between components without passing the data through each level of the component tree manually. It allows you to create global data stores that can be accessed by any component in the application.

With Context API, you can define a context object that holds the data and pass it to any component that needs access to that data. The context object can be updated by any component in the application, and changes will be automatically propagated to all components that are subscribed to that context.

Context API is especially useful for large applications with deeply nested component hierarchies where passing data through multiple levels of components can become cumbersome and error-prone. By using context, you can simplify the data flow and make it easier to manage state across the application.

 

Here is a simple example of how to use Context API in React:

1. Define the context object
First, you need to define a context object using the createContext() method from the React library. The context object should include an initial value that will be used if no value is provided by a parent component.

import { createContext } from 'react';

const MyContext = createContext('default value');

 

2. Create a provider component
Next, you need to create a provider component that will wrap the components that need access to the context. The provider component should accept a value prop, which will be passed down to the child components.

 

import React, { useState } from 'react';
import MyContext from './MyContext';

function MyProvider(props) {
  const [myValue, setMyValue] = useState('initial value');

  return (
    <MyContext.Provider value={{ myValue, setMyValue }}>
      {props.children}
    </MyContext.Provider>
  );
}

 

3. Access the context in child components
Finally, you can access the context in any child component by using the useContext() hook. This hook takes the context object as an argument and returns the current value of the context.

 

 

import React, { useContext } from 'react';
import MyContext from './MyContext';

function MyComponent(props) {
  const { myValue, setMyValue } = useContext(MyContext);

  return (
    <div>
      <p>{myValue}</p>
      <button onClick={() => setMyValue('new value')}>
        Update value
      </button>
    </div>
  );
}

In this example, MyComponent can access the myValue and setMyValue properties from the context object defined in MyProvider. The component can update the value of the context by calling the setMyValue function. When the value is updated, all components that are subscribed to the context will receive the updated value.