- Setup - General instructions to install Node 16;
- Bootstrapping - Instructions to get the application running;
- A few goodies - Brief list of useful packages;
- Building a Weather app - Detailed description of the desired functionallities;
- Next steps - Suggestions of further challenges to enhance our Weather app.
Make sure you have Node v15 installed and ready to go, either download it directly from its download page, or through nvm
, which we recommend for both Linux and macOS.
Follow the next set of instructions to complete your setup with nvm
.
nvm install 16.6.2
nvm use 16.6.2
node --version
> v16.6.2
npm install -g npx
create-react-app
is a Node package curated by Facebook, that can be used to create a new React project, with the required support scripts and dependencies.
Through npx
we able to download and execute the package in a single action to create a new react project:
npx [email protected] weather-app
Note: On the first time npx will ask you to install the create-react-app package. Just click y and it will install and create your first react app.
And we have just created a new React project, that is almost ready to run. Please take a few minutes browsing the generated files inside weather-app folder, mainly:
package.json
- npm relies on this file for all of its iteration, from dependencies to available scripts;./src/App.js
- The main component of the generated application;./src/index.js
- Root of our web application, whereApp
is mounted;README.me
- Description of the generated project.
We can now run the application on development mode.
npm start
The web application should be now running and accessible on a browser through http://localhost:3000.
The page will reload if you make any changes and will show any errors in the browser's console.
Before we start hacking away our weather application, we will take advantage of a few packages that will speed up our development.
Material UI is a well known and very popular library of polished UI components that can easily be reused. All components follow an underlying theme, keeping our application with a consistent look and feel.
npm install @mui/[email protected] @emotion/[email protected] @emotion/[email protected]
Usage example:
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
const Title = styled(Typography)(() => `
fontSize: 18;
color: black;
`)
const Footer = styled('p')(() => `
fontSize: 8;
color: grey;
`)
export function SimpleCard() {
return (
<Card>
<CardContent>
<Title variant="h1">
Title
</Title>
<Typography variant="h5" component="h2" gutterBottom>
Subtitle
</Typography>
<Typography color="textSecondary">
Main content of the card
</Typography>
<Footer variant="body2" component="p">
A footer note
</Footer>
</CardContent>
<CardActions>
<Button size="small">Call to action</Button>
</CardActions>
</Card>
);
};
Material UI brings along dozens of components that cover a fairly amount of scenarios and the following root components will be quite useful during our development:
- styled - Utility function to style existing components, like Typography, or dom elements, like paragraphs;
- Autocomplete - Input with suggestions;
- Card - Material UI's root Card component;
- Grid - Easy manage a 12 columns layout;
- Typography - Component for all kind of text, with a huge set of variants to fit titles, paragraphs, etc;
date-fns is a popular package to handle and format dates.
npm install [email protected]
Usage example
import { format, parseISO } from 'date-fns'
const date = new Date(); // "Mon Nov 18 2021 21:20:02 GMT+0000 (Western European Standard Time)"
format(date, 'EEE d MMMM, h:mm:ss a') // "Mon 18 October, 9:20:02 PM"
format(parseISO("2021-11-18"), 'MMMM do yyyy') // "November 18th 2021"
More details about the available formats can be found on date-fns's docs.
Our weather app will consist of an SPA that will display the current forecast details and the next few days' forecast, for a selected location.
Users should be allowed to search for a location. This search will guide our users, displaying possible locations containing the users' query as he types.
Upon selecting a location, he should be able to check the forecast for the next 5 days and the details of today's weather report.
We will rely on MetaWeather's data. It provides endpoints to search for a location from a provided query string and another to fetch the weather report of a location, through its WOEID - Where on Earth ID. We have sampled the response for a possible search and location's forecast to simplify our development.
In the sample
folder, the next examples are available:
search.json
- Available locations for the search queryli
;weatherReports.json
- Weather reports for the locations available insearch.json
.
They can be imported and used in the application:
import searchSample from "./sample/search.json";
The next sections we will further detail the behavior and role of each element of our interface and supply tips in how to proceed. These tips are only tips and nothing more, feel free to explore Material-UI's components to shape the interface to suit your taste.
Before jumping into more concrete blocks of our application we have to setup a general layout, it will work as the foundations of the interface and split it into sections where we will place the other elements.
Grid from Material-UI is quite handy to achieve such behavior. It creates a wrapper container with 12 columns, allowing a quick establishment of sections with a fixed set of columns for a specified resolution. Grid will adjust its sub-components to fit the current resolution following the provided props.
This element consist of an input to type a location.
It will guide our user, displaying suggestions of locations that suit the provide input.
Material UI's Autocomplete component suits this task. It is capable of providing suggestions from a existing source and filtering them to match the input. Also provides a mechanism to establish the behavior for click actions.
Upon selecting a location trough the search, this section should render a set o elements to display the forecast for the next 5 days.
This should display a summary, with the following information:
- Day of the forecast;
- Weather forecast, sunny, rainy, etc;
- Max temperature;
- Min temperature;
The forecast for the next 5 days can be found in the sample file, weatherReports.json
, in the attribute consolidated_weather
.
Card component are simple and effective ways of displaying this kind of information.
This section should display the details of today's forecast, with all the information available, from current temperature to degree of humidity.
Until now, our forecast comes only from the sample files, but ideally, we should be fetching the data directly from MetaWeather, but this is where it gets complicated.
MetaWeather API doesn't have CORS enabled, so our browser will refuse to fetch data from it. As a work around, we can use local-cors-proxy
to proxy the request and trick our browser.
npx local-cors-proxy --origin http://localhost:3000 --proxyUrl https://www.metaweather.com
In order to perform request to MetaWeather, axios is definitely the right tool for the job.
react-query streamlines the usage of axios, so that it works as any other hook.
npm install [email protected] [email protected]
Simple example, performing a request to MetaWeather to fetch a locations data through its woeid:
import axios from 'axios';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
const queryClient = new QueryClient();
function ForecastLocationDetails({ woeid }) {
const {
data,
isLoading,
isError,
error
} = useQuery(
['getLocationForecastDetails', woeid],
() => {
return axios.get(`http://localhost:8010/proxy/api/location/${woeid}/`)
.then(({ data } = {}) => data)
});
if(isError) {
return <h3>{error.message}</h3>
}
if(isLoading) {
return <h1>Loading...</h1>;
}
return (
<>
<h1>{data.title}</h1>
<h3>{data.location_type}</h3>
<h2>{data.consolidated_weather[0].weather_state_name}</h2>
<h2>{`${data.consolidated_weather[0].the_temp}º`}</h2>
</>
);
}
export default function App() {
const woeid = "638242" // Berlin
return (
<QueryClientProvider client={queryClient}>
<ForecastLocationDetails woeid={woeid}/>
</QueryClientProvider>
)
}
A map would be a great addition to our weather app. Since MetaWeather provides the latitude and longitude of a location, it would be nice to focus that location on a map.
It would be a bit awkward to make plans for a nice weekend BBQ based on the forecast for Lisbon, Florida or some other Lisbon, just in the USA.
As you can imagine, there are quite a few components at our disposal, and we suggest the usage of react-mapbox-gl.
It is quite simple to integrate and to display a point at a certain latitude and longitude.
Feel free to play with their demos to get a feeling about how it works and look around their documentation.
The most important components to focus upon a location would be ReactMapboxGl
, Layer
and Feature
.
We can install it into our project with:
npm install react-mapbox-gl mapbox-gl
In order to use react-mapbox-gl, we will need an accessToken, that we already requested:
pk.eyJ1IjoicHNpbHZhaWMiLCJhIjoiY2tobmI5YTRlMDAzbTMxcGV6NDk3ZHNrdCJ9.mpgz1tj9j8cLrhrsZ5hlhw
The snippet bellow we displays how we can create a Map, with the provide accessToken, that will focus upon Lisbon, Portugal and place a small marker:
import ReactMapboxGl, { Layer, Feature } from 'react-mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
const Map = ReactMapboxGl({
accessToken:
'pk.eyJ1IjoicHNpbHZhaWMiLCJhIjoiY2tobmI5YTRlMDAzbTMxcGV6NDk3ZHNrdCJ9.mpgz1tj9j8cLrhrsZ5hlhw'
});
function MyMap() {
return (
<Map
style="mapbox://styles/mapbox/streets-v9"
containerStyle={{
height: '100%',
width: '100%'
}}
>
<Layer type="symbol" id="marker" layout={{ 'icon-image': 'marker-15' }}>
<Feature coordinates={[38.725670, -9.150370]} />
</Layer>
</Map>
)
}