React Tutorial — Build a Weather App from Scratch — Part 1
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:
- Bootstrapping an app with
create-react-app
- Using React with TypeScript
- React Hooks
- Async Communication to the Open Weather API
- Basic Styling
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).
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:
- Remove the contents of
App.css
andindex.css
- Remove the import of
logo.svg
fromApp.tsx
- Delete
App.test.tsx
andlogo.svg
- 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.