React Tutorial — Build a Weather App from Scratch — Part 1

Eamonn Boyle
The Startup
Published in
10 min readFeb 24, 2021
Brick Tamland — Weather

In this tutorial I’ll provide a complete introduction to using React and TypeScript to build a client-side web app. Together we’ll create a simple Weather App that allows you to search for locations and displays its current weather and 24 hour forecast.

Along the way, you’ll learn:

You can find the code here on GitHub. The tutorial is broken up into 6 steps spread over 2 blog posts and the code repository contains the end result of each step in its own folder for easy comparison. Part 2 is available here.

If you want to a good introduction to React, then follow along and try to implement this app for yourself. Also check out what we cover in our full React course.

Requirements

  • NodeJS
  • An editor
  • Basic familiarity with using the terminal

Step 1: Boot-strapping an App with create-react-app

create-react-app is a tool for creating a React project that hides a lot of the complexity of modern JS/TS build systems. This next command will create a new folder for our project so run this in the location you want the project to live. Since we are going to use TypeScript we can specify this template:

npx create-react-app weather-app --template typescript

cd weather-app

This will create the project folder and perform an npm install (or yarn if it detects that it’s installed).

Folder Structure

Inside package.json

Looking inside package.json you will notice that the project has only a few dependencies and also some useful scripts for starting, building and testing the project. The scripts make use of react-scripts which is designed to hide the complexity of what is really going on under the hood.

We actually have 100s of dependencies and the build system is very complex but this is all hidden behind react-scripts. We can start the development server by running:

npm start

This should open the browser and start to show your app that is running on http://locahost:3000:

Important Files and Folders

  • package.json - The definition of our dependencies and build scripts.
  • tsconfig.json - TypeScript compiler settings and configuration.
  • public - This contains any static assets such as images, icons etc. It also includes the first page, index.html.
  • public/index.html - The HTML entry point or starting page for your app.
  • src/index.tsx - The entry point for our executable code.

Note, with React we define our page content in code and the framework will handle rendering. So we don’t need to modify the index.html file under normal circumstances. At build time script import lines are added to this file and it is the running code that updates the view in memory. index.tsx is where we kick off a React rendering process for our app.

The initial render code here use a default component, App found in App.tsx.

You can see that React components can be written as simple functions that return some view. We call them “functional components”. You can write your components as classes, but functions are preferred.

The view is written using JSX (hence the tsx extension rather than ts). JSX is a syntax extension for JavaScript/TypeScript that allows you to write HTML like tags directly in code. This is trivially transformed into object creations at build time and you can use them anywhere an expression is required (assignments, returns, function calls etc). You could create the objects directly but JSX is neater and more intuitive for building out nested view structures.

There are some differences between HTML and JSX so that we don’t get conflicts with normal JS/TS code when it is translated. For example, the class attribute is called className and we use camel-case rather than kebab-case.

As long as the function begins with a capital letter (Pascal Case) you can then use it as a component/tag in other JSX, such as in index.tsx:

You’ll also notice that we’re importing a CSS file and SVG image directly in code. This is not standard JS/TS but react-scripts is pulling in Webpack plugins that support this.

Our development server has hot-reload capabilities so we can strip our app right back and see it update live:

  1. Remove the contents of App.css and index.css
  2. Remove the import of logo.svg from App.tsx
  3. Delete App.test.tsx and logo.svg
  4. Replace the definition of the App function (component) with:

If everything has worked, when you save, the browser will have automatically refreshed and show:

Step 2 — Show a Table of Locations

Let’s add an input where a user can search for a location, and add that location to a table. To start with, the location is going to be a string and the list of all locations in the table will be a string array.

Build out the view by replacing the App component with:

As you can see, this is simply building out a view in the same way we would with HTML. When you save the app should look like:

Notice, however, that typing in the input box does not work. This is because we have specified the value of the box as the constant string ‘Paris’ and React is controlling the rendering of the view, including the contents of the box.

Introducing State

So we need to introduce some State to represent the contents of the input. State is data that is bound to the view — only when state changes will React rerender and update the view. State is the source of truth in your React apps.

We introduce state in functional components using the useState hook. Check out Ross Jenkins’ great intro to React Hooks. Hooks have to go at the beginning of a function and the useState hook returns a tuple containing the current value of the state and a function to update the state which we can destructure in one line:

'Paris' is the initial value and the types are inferred. There are no identifiers or names to identify particular pieces of state, the constant order of calls is how the state is identified. That is why hooks go at the start of the function and can’t be inside any control flow such as if.

Next we need to include the value locationSearch into our JSX. We can embed expressions in JSX using {}:

Notice that the value attribute assignment does not use quotes. When assigning attributes with expressions in React we always use attribute={expression}, not attribute="{expression}".

Finally, we need to add an onChange handler (note the camel-case) to call the setLocationSearch function whenever the control changes. Again, we use {} to supply an arrow function expression:

So when the change handler fires the state will be set, which in turn triggers a rerender.

Mapping Rows in a Table

Let’s continue and add some state for the rows of the table.

To map this into the view we need to have a tr row for every entry in the array, so we literally do a map:

Note how within the map arrow function we can return nested JSX, and that JSX can in turn embed a nested {} expression.

This interleaving of code and view is very useful. We still need to make one final change. With lists of elements React wants a unique key for each entry so that it can efficiently perform change tracking. Later we will use the location id, but for now we can simply use the index within the table:

If everything has worked then the view should now look like it did with static content:

Adding a Location

To support adding a location we need to trigger an action when the search button is clicked. This is similar to the input handler but instead of an onChange event we now handle an onClick event. Note that we modify the locations array by setting a completely new state value, not by mutating. This is easy using the spread operator. We also use the locationSearch as the data to add, NOT the input control. In React, state is the source of truth.

Let’s tidy the code up a bit further:

  • Break the handler out as a local const
  • Disable the button if locationSearch is blank
  • Set the search text to blank after searching

Further Tidying Up

We can remove the initial dummy data from our useState hooks.

Note that for the locations state the type of the initial data, an empty array, cannot be inferred so we explicitly specify the generic parameter, string[]. For the locationSearch it is automatically inferred as string.

Let’s also add Bootstrap for some basic styling. First, add the package to our project,

npm install --save bootstrap

Next, import the CSS — we can do this directly in code in index.tsx since react-scripts supports this.

Now we can make use of Bootstrap’s classes on our UI elements. Note the use of className instead of class.

The app immediately looks better.

Step 3: Breaking out Components

Our app currently only has a single component, App. Let’s break the search and table out into their own components. This is common practice for building large UIs where we structure the application as a tree of components:

Location Search Component

We’ve already seen with App that components can be written simply as functions. We just need to remember that the name must begin with a capital letter. We’ll also typically create these in their own file.

Let’s begin with a LocationSearch component by creating a file called LocationSearch.tsx and placing the following code inside it:

Here I’ve basically cut and pasted this search part of the view out of the App.tsx file. I’ve added the type FC to the arrow function to strongly type my definition - this is a React type specifying a functional component.

Note I’ve also imported React on the first line. Prior to React 17 this line was required in all files that use JSX. This is due to the fact that tags were ultimately converted to calls to React.createElement. In React 17 this transform has changed and this import is not required.

Next I have to think about my state — should the locationSearch string state belong to App or LocationSearch? In this case, the input text is transient and I am happy to move it into this new component.

The onClick handler, however, modifies the locations state. I don’t want to the locations state in this control as other parts of my app will use it too so instead I will leave that state in App. I have to communicate from this child component back up to the App whenever the search button has been clicked - we do this by defining a callback prop.

Adding Props to Components

A prop is an input to a component specified as an attribute in JSX. For example:

All props are grouped into an object that is passed to our functional component as a parameter. We can think of state as belonging to the component and is read write. Props are readonly and don’t belong to the component, they are passed in. By passing in functions as callback props we can attach event handlers — in this case the handler for onSearch.

In TypeScript we create an interface for all props and specify it as the generic parameter of FC:

Note that the callback takes a string rather than a click MouseEvent parameter. This is another useful thing about having components, we can hide low level details and build cleaner interfaces.

By specifying the generic parameter on FC, the props function parameter will be correctly typed. We can directly destructure within the parameter list and consume onSearch.

Now that we have our callback prop coming into our component we can invoke it when the search button is clicked.

Finally, we can update App.tsx to use this new component:

Location Table Component

For the table component, it is a similar process.

  • Create a file called LocationTable.tsx
  • Move the table view from App.tsx into a new component in the file
  • Add an interface for the props — in this case the locations to display

App.tsx can then be updated to consume this new component:

Next Steps

At this point we have accomplished a lot,

  • Bootstrapped a React project from scratch
  • Learned the fundamentals of React — Components, State and Props, Binding values, Basic hooks, Adding event handlers
  • Utilised TypeScript for safety

In Part 2 we’ll build on this to:

  • Integrate a real weather service
  • Use asynchronous communication
  • Apply more hooks

So be sure to come back and check out Part 2. You might also want to check out what we cover in our full React course.

Originally posted here.

--

--

Eamonn Boyle
The Startup

15 years working as a developer. For the last 5 years I've been working as a trainer and coach, authoring and delivering courses on a range of topics.