Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / React

Using Create-React-App and .NET Core 3.0 to Build a React Client (Global Weather) - Part 1

5.00/5 (29 votes)
5 Feb 2020CPOL13 min read 131.8K   2.3K  
Global Weather - React App with ASP.NET Core 3.0
In this article, we will build React websites that seamlessly work with ASP.NET Core 3.0 as the back-end server. By the end of the article, we will show you how to create a weather Client with Create-React-App, implementation using Form Components, styling UI with Bootstrap, and call the .NetCore API.

Introduction

I’ve introduced how to build Angular App with .NET Core in Global Weather – Angular 7 App With .NET Core series (Part 1, Part 2 and Part 3). Now let’s talk about another popular technology React. React, Angular have become incredibly popular because these single-page app frameworks building apps that run self-contained in a browser page is hugely powerful.

React vs. Angular

Angular and React have many similarities and many differences. Angular is an MVC framework and structures the application very well, but you have less flexibility. React only provides the “view” in MVC – you need to solve the M and C on your own. Due to this, you can choose any of your own libraries as you see fit.

Both React and Angular are component based. A component receives an input, and returns a rendered UI template as output.

React’s use of a virtual DOM is to make it so fast. A virtual DOM only looks at the differences between the previous and current HTML and changes the part that is required to be updated. Angular uses a regular DOM. This will update the entire tree structure of HTML tags until it reaches the user’s age.

React decided to combine UI templates and inline JavaScript/Typescript logic, which no company had ever done before. The result is called “JSX”(javascript) or “TSX” (typescript). JSX/TSX is a big advantage for development, because you have everything in one place, and code completion and compile-time checks work better.

React uses one-way binding, and Redux is often used as the solution to handle state. Angular uses two-way binding. One-way data binding means that React is more performant than Angular and also makes debugging a React app easier than debugging an Angular app.

Create-React-App

The best solution to build a React app is using create-react-app. Create-React-App is maintained by Facebook. Create React App sets up your development environment so that you can use the latest JavaScript/TypeScript features, provides a nice developer experience, and optimizes your app for production. You’ll need to have Node >= 8.10 and npm >= 5.6 on your machine.

To create a JavaScript project, run:

PowerShell
npx create-react-app my-app
cd my-app
npm start

To create a typescript project, run:

PowerShell
npx create-react-app my-app --typescript
cd my-app
npm start

Create ASP.NET Core Web Project from Visual Studio 2019

Open your Visual Studio 2019 -> Create New Project -> Select ASP.NET Core Web application.

Image 1

Give the project name as “GlobalWeatherReact”.

Image 2

Click “Create”, and in the next window, select ASP.NET Core 3.0 and Empty as shown below:

Image 3

Click “Create”, then a default ASP.NET Core project is created.

Image 4

Leave it for the time being. After we create a React client, need back here to change some code.

Create Weather Client with Create-React-App

Once the API project is created, open the Powershell and navigate to the GlobalWeather project folder and run the following command:

npx create-react-app weatherclient --typescript

Image 5

This will create a latest version of React application. Now the solution structure should be like this:

Image 6

TSX is typescript of JSX. JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, though the semantics of that transformation are implementation-specific. JSX rose to popularity with the React framework. TypeScript supports embedding, type checking, and compiling JSX directly to JavaScript.

Now we need make some changes in ASP.NET Core project to make it smoothly integrate with React client.

First, install ASP.NET Core SPA Services and ASP.NET Core SPA Services Extensions in Nuget Manager.

Image 7

Open Startup.cs, add SpaStaticFiles in ConfigureServices method.

C#
services.AddMvc(option => option.EnableEndpointRouting = false);
services.AddSpaStaticFiles(configuration =>
{
    configuration.RootPath = "weatherclient/build";
});

And add the below lines in the Configuration method:

C#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc();
app.UseSpa(spa =>
{
    spa.Options.SourcePath = Path.Join(env.ContentRootPath, "weatherclient");

    if (env.IsDevelopment())
    {
        spa.UseReactDevelopmentServer(npmScript: "start");
    }
});

OK. Now just run it, click “IISExpress”.

Image 8

Wow, the React client is starting with ASP.NET Core API project!

React Router and Component

The Hello World react app is a single-component app. But most real world apps are multi-component app. React Router is a collection of navigational components that compose declaratively with your application. In single page apps, there is only a single HTML page. We are reusing the same HTML page to render the different components based on the navigation.

React Component

React lets you define components as classes or functions. Components defined as classes currently provide more features. To define a React component class, you need to extend React.Component.

Each component has several “lifecycle methods” that you can override to run code at particular times in process. The component life cycle is listed as below.

  • Mounting

    These methods are called in the following order when an instance of a component is being created and inserted into the DOM.

    JavaScript
    constructor()
    static getDerivedStateFromProps()
    render()
    componentDidMount()
  • Updating

    An update can be caused by changes to props or state. These methods are called in the following order when a component is being re-rendered.

    JavaScript
    static getDerivedStateFromProps()
    shouldComponentUpdate()
    render()
    getSnapshotBeforeUpdate()
    componentDidUpdate()
  • Unmounting

    This method is called when a component is being removed from the DOM.

    JavaScript
    componentWillUnmount()
  • Error Handling

    These methods are called when there is an error during rendering, in a lifecycle method, or in the constructor of any child component.

    JavaScript
    static getDerivedStateFromError()
    componentDidCatch()

First, we add a Home component first, move the code from App.tsx to Home component.

Create “components” folder under “weatherclient/src”. Then add Home.tsx.

Image 9

Home component extends React.Component. We copy render() from App.tsx.

JavaScript
import React from 'react';
import logo from '../logo.svg';

class Home extends React.Component {

    render() {
        return (
            <div className="App">
                <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <p>
                        Edit <code>src/App.tsx</code> and save to reload.
                    </p>
                    <a
                        className="App-link"
                        href="https://reactjs.org"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        Learn React
                    </a>
                </header>
            </div>
        );
    }
}

export default Home;

React-router-dom

React-router-dom exports DOM-aware components, like <Link> (which renders an <a>) and <BrowserRouter> (which interacts with the browser's window.history).

React-router-dom re-exports all of react-router's exports, so you only need to import from react-router-dom in your project.

Now we install react-router-dom first. In Powershell, install react-router-dom under “weatherclient” folder.

PowerShell
npm install @types/react-router-dom react-router-dom --save

Add Router to App

Create “router” folder under “weatherclient/src”, then add index.tsx.

JavaScript
import React from "react";
import { Router, Route, Switch, Link, NavLink } from "react-router-dom";
import * as createHistory from "history";
import Home from "../components/Home";

// Instead of BrowserRouter, we use the regular router,
// but we pass in a customer history to it.
export const history = createHistory.createBrowserHistory();

const AppRouter = () => (
    <Router history={history}>
        <div>
            <Switch>
                <Route path="/" component={Home} />
            </Switch>
        </div>
    </Router>
);

export default AppRouter;

Then we go back to change App.tsx.

JavaScript
import React from 'react';
import "./App.css";
import AppRouter from "./router";
const App: React.FC = () => {
    return (
        <AppRouter />
    );
}
export default App;

Basically, it removes fall rendering code, and replace with AppRouter.

Now, click “IIS Express” to run it again.

Image 10

App Router is working. It automatically navigates to the Home component.

Function Component Vs. Class Component

You may notice App Component is a function component and Home Component is a class component.

But what’s the difference between function component and class component?

Functional components are basic JavaScript/TypeScript functions. These are typically arrow functions but can also be created with the regular function keyword. React lifecycle methods cannot be used in functional components. There is no render method used in functional components. Functional components should be favored if you do not need to make use of React state.

Class components make use of ES6 class and extend the Component class in React. React lifecycle methods can be used inside class components. Class components have internal state. “this.state” read state and “this.setState” updates state.

Implement WeatherDetails and Form Components

What We Want to Build?

After introducing the basic react stuff, now it’s the time to build our weather app. We’ll build a very similar app as my series articles Global Weather - Angular 7 App with .NET Core 2.2.

Image 11

So basically, this app has two parts. One is a panel which shows weather details. The other is an input form where we can input country and city. Now let’s build the app step by step.

WeatherDetails Component

Add WeatherDetails.tsx under “components” folder with the below code:

JavaScript
import React from 'react';
class WeatherDetails extends React.Component {
    render() {
        return (
            <div>
                Weather Details
            </div>
        );
    }
}
export default WeatherDetails

Add Form.tsx under “components” folder with the below code:

JavaScript
import React from 'react';
class Form extends React.Component {
    render() {
        return (
            <div>
                <input id="country" type="text" name="country" placeholder="  Country... " />
                <input id="city" type="text" name="city" placeholder="  City... " />
            </div>
        );
    }
}
export default Form

Then change Home component to use WeatherDetails and Form.

JavaScript
import React from 'react';
import Form from './Form';
import WeatherDetails from './WeatherDetails';
class Home extends React.Component {
    render() {
        return (
            <div className="App">
                <WeatherDetails />
                <Form />
            </div>
        );
    }
}
export default Home;

Click “IISExpress” to run it.

Image 12

That’s it. Now we create types first, then back to components to do more work.

Types

We’re developing a website to display weather information via AccuWeather REST API. The user can select whatever location and show current weather information.

Create an account to obtain an API key to use against the APIs.

Users should be able to narrow their location search by country.

The API reference introduces all response details for the different API request. For our app, we only care about country, city and current weather.

So according to json response, we create the corresponding type, Country, City and CurrentCondition types.

Create “types” folder under “weatherclient/src”. Add Country.ts to “types” folder.

Then copy the below code to Country.ts.

JavaScript
export interface Country {
    ID: string;
    EnglishName: string;
};

Then add City type in City.ts.

JavaScript
import { Country } from './Country';
export interface City {
  Key: string;
  EnglishName: string;
  Type: string;
  Country: Country;
};

Add CurrentCondition type in CurrentCondition.ts.

export interface CurrentCondition {
    LocalObservationDateTime: string;
    WeatherText: string;
    WeatherIcon: number;
    IsDayTime: boolean;
    Temperature: Temperature;
};
export interface Metric {
    Unit: string;
    UnitType: number;
    Value: number;
};
export interface Imperial {
    Unit: string;
    UnitType: number;
    Value: number;
};
export interface Temperature {
    Imperial: Imperial;
    Metric: Metric;
};

For WeatherDetails component, I don’t want to bind CurrentCondition directly, so create a view model class, Weather.

Add Weather.ts to “types” folder.

JavaScript
import { CurrentCondition } from './CurrentCondition';
import { City } from './City'
import { Constants } from '../Constants';
export class Weather {
    public location?: string;
    public weatherIcon?: any;
    public weatherText?: string;
    public temperatureValue?: number;
    public temperatureUnit?: string;
    public isDaytime?: boolean;
    public error?: string;
    public constructor(currentConditions: CurrentCondition, city: City) {
        this.location = city.EnglishName!;
        this.weatherText = currentConditions.WeatherText;
        this.isDaytime = currentConditions.IsDayTime;
        if (currentConditions.WeatherIcon) {
            let icon = currentConditions.WeatherIcon.toString();
            if (icon.length === 1)
                icon = "0" + icon;
            this.weatherIcon = `${Constants.weatherIconUrl}${icon}-s.png`;
        }
        this.temperatureValue = currentConditions.Temperature.Metric.Value;
        this.temperatureUnit = currentConditions.Temperature.Metric.Unit;
    }
}

Here, you can see we make up weather icon as an HTML link URL. You can find all weather icons from Accure Weather Icons.

Styling UI With Bootstrap, React-Bootstrap and React-Bootstrap-Typeahead

Bootstrap is an open source toolkit for developing with HTML, CSS, and JS. Quickly prototype your ideas or build your entire app with our Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful plugins built on jQuery.

React-Bootstrap replaces the Bootstrap JavaScript. Each component has been built from scratch as a true React component, without unneeded dependencies like jQuery.React-Bootstrap provides a bunch of components like Button, Form, Accordion, Badge, Alter, Dropdown.

React-Bootstrap-Typeahead is a React-based typeahead that relies on Bootstrap for styling. It supports both single- and multi-selection.

We use bootstrap for styling and React-Boostrap input and button components. Also, we use React-Bootstrap-Typeahead for Country input.

Install Bootstrap, React-Bootstrap and React-Bootstrap-Typeahead under “weatherclient” folder.

JavaScript
npm install react-bootstrap bootstrap –-save
npm install react-bootstrap-typeahead @types/react-bootstrap-typeahead --save

After installing bootstrap, go to index.tsx under src folder to add the below line:

JavaScript
import 'bootstrap/dist/css/bootstrap.min.css'

This line apples bootstrap styling to the whole app.

Now we can style our app, replace App.css with the following styles:

CSS
.panel {
    padding-left: 0px;
    padding-top: 10px
}
.field {
    padding-left: 10px;
    padding-right: 10px
}
.city {
    display: flex;
    background: linear-gradient(90deg, rgba(2,0,36,1) 0%, 
    rgba(25,112,245,0.6399510487788865) 0%, rgba(0,212,255,1) 100%);
    flex-direction: column;
    height: 40vh;
    justify-content: center;
    align-items: center;
    padding: 0px 20px 20px 20px;
    margin: 0px 0px 50px 0px;
    border: 1px solid;
    border-radius: 5px;
    box-shadow: 2px 2px #888888;
    font-family: 'Merriweather', serif;
}
.city h1 {
    line-height: 1.2
}
.city span {
    padding-left: 20px
}
.city .row {
    padding-top: 20px
}
.weatherError {
    color: #f16051;
    font-size: 20px;
    letter-spacing: 1px;
    font-weight: 200;
}

Rework Components with IState and IProp

Form Component

We change Form component to a state component. React class component has a built-in state object. The state object is where you store property values that belong to the component. When the state object changes, the component re-renders.

Now we add state and props to our form component.

JavaScript
import React from "react";
import { Button, FormControl } from 'react-bootstrap';
import { AsyncTypeahead, Typeahead } from 'react-bootstrap-typeahead';
import { Country } from '../types/Country';
import { City } from '../types/City';
interface IState {
    city: City;
    country: Country;
    cities: City[];
    searchText: string
};

interface IProps {
    /* The http path that the form will be posted to */
    countries: Country[];
}
class Form extends React.Component<IProps, IState> {
    constructor(props: IProps) {
        super(props);
        this.state = {
            city: {} as City,
            country: {} as Country,
            cities: [],
            searchText: ""
        }
    };
    handleSubmit = async (e: any) => {
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <div className="container-fluid">
                    <div className="row">
                        <div className="col-sm-4 form-group">
                            <Typeahead
                                id="country"
                                labelKey="EnglishName"
                                options={this.props.countries}
                                onChange={(s) => this.setState({ country: s[0] } as IState)}
                                placeholder="Country..."
                            />
                        </div>
                        <div className="col-sm-4 form-group field">
                            <FormControl id="city" type="text" name="city" 
                             onChange={(e: any) => this.setState
                             ({ searchText: e.target.value })} placeholder="  City... " />
                        </div>
                        <div className="col-sm-2 form-group field">
                            <Button variant="primary" type="submit"> Go </Button>
                        </div>
                    </div>
                </div>
            </form>
        );
    }
};
export default Form;

Here, we use react-bootstrap-typeahead for country input, and use react-boostrap FormControl for city text input, also use react-bootstrap Button for “Go” button. The one thing I think I need mention is OnChange event of input fields.

As I mentioned before, React is one way binding, which means when you change the UI field value, binding data model is not get updated automatically as Angular. That’s why we need update the binding data model via onChange event. Also react doesn’t allow you to change component state directly, you need to call setState method to update state.

We leave handleSubmit as empty function now, and will come back later.

WeatherDetails Component

WeatherDetails component is pretty straight forward, just binding Weather view model and display.

JavaScript
import React from 'react';
import { Weather } from '../types/Weather'
interface IProp {
    weather: Weather,
};
class WeatherDetails extends React.Component<IProp> {
    render() {
        const weather = this.props.weather;
        return (
            <div>
                <div className="city col-sm-9">
                    {
                        weather.location && <div>
                            <h1>{weather.location}</h1>
                            <div className="row">
                                <table>
                                    <tbody>
                                        <tr>
                                            <td>
                                                {
                                                    weather.weatherIcon && 
                                                    <img src={weather.weatherIcon} 
                                                    className="img-thumbnail" />
                                                }
                                            </td>
                                            <td>
                                                <span>{weather.weatherText}</span>
                                            </td>
                                        </tr>
                                        <tr>
                                            <td>
                                                {weather.isDaytime && <span>
                                                    Daytime
                                    </span>}
                                                {!weather.isDaytime && <span>
                                                    Night
                                    </span>}
                                            </td>
                                            <td>
                                                <span>{weather.temperatureValue}&deg;
                                                      {weather.temperatureUnit}</span>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    }
                </div>
                {
                    weather.error && <p className="weatherError">
                        {weather.error}
                    </p>
                }
            </div>
        );
    }
};

export default WeatherDetails;

Here, we pass “weather” to WeatherDetails component as prop. In render function, binding prop to HTML fields. Also you can see we’re using conditional rendering.

Conditional rendering in React works the same way conditions work in JavaScript. Use JavaScript operators like if or the conditional operator to create elements representing the current state, and let React update the UI to match them. In a conditional render, a component decides based on one or several conditions which elements it will return. When a component has conditional rendering, the instance of the rendered component can have different looks.

Home Component

First, define state interface for Home component.

JavaScript
interface IState {
    weather: Weather,
    countries: Country[],
    city?: City
}

Then initialize the state in constructor of component.

JavaScript
public state: IState = {
    weather: {
        error: ""
    } as Weather,
    countries: [],
    city: undefined
}

In render function, pass state values to WeatherDetails and Form components.

JavaScript
render() {
    return (
        <div className="container content panel">
            <div className="container">
                <div className="row">
                    <div className="form-container">
                        <WeatherDetails weather={this.state.weather} />
                        <Form countries={this.state.countries} />
                    </div>
                </div>
            </div>
        </div>
    );
}

Now click “IIS Express” to run again.

Image 13

How to Consume Restful API In React

Most modern web applications make use of the REST Protocol to communicate with each other. To achieve this, data is sent as JSON (JavaScript Object Notation) to the API. In turn, the API returns a JSON payload which can be static or dynamic data.

The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest, but the new API provides a more powerful and flexible feature set. Fetch API is builtin package of Create-React-App. So we don’t need install anything, just use it directly.

Add Constants.ts, put the API URLs and API key in Constants class.

export class Constants {
    static locationAPIUrl = 'https://dataservice.accuweather.com/locations/v1';
    static citySearchAPIUrl = 'https://dataservice.accuweather.com/locations/v1/cities/';
    static currentConditionsAPIUrl = 'https://dataservice.accuweather.com/currentconditions/v1';
    static weatherIconUrl = 'http://developer.accuweather.com/sites/default/files/';
    static apiKey = 'XXXXXXXXXXXXXXXXXX';
}

You should replace apiKey with your API key.

Get All Countries

Call Country List endpoint of Accure Weather Locations API.

Resource URL,

  • https://dataservice.accuweather.com/locations/v1/countries

Add the below function to Home component.

JavaScript
async getCountries(): Promise<Country[]> {
    try {
        const res = await fetch
                    (`${Constants.locationAPIUrl}/countries?apikey=${Constants.apiKey}`);
        return await res.json() as Country[];
    } catch (error) {
        console.log(error);
        return [];
    }
}

Here, we call http get by “fetch”, and json deserialize the result to array of Country. In short, the Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

Now we have getCountries function, but where do we need to call it? We talked react component life cycle before, we need call getCountries() when Home component is being created and inserted into the DOM. Then the best location is componentDidMount().

JavaScript
async componentDidMount() {
    try {
        const countries = await this.getCountries();
        await this.setStateAsync({ countries: countries } as IState);
    } catch (error) {
    }
}

Add setStateAsync as well.

JavaScript
async setStateAsync(state: IState) {
    return new Promise((resolve: any) => {
        this.setState(state, resolve);
    });
}

Why do we use async await?

Async and Await are extensions of promises. Async functions enable us to write promise based code as if it were synchronous, but without blocking the execution thread. The await operator is used to wait for a Promise.

OK. Now we run the app again.

Image 14

Because we are binding “Country” typeahead with “countries” prop in Form component, and passing countries from Home component to Form component, the typeahead shows options as expected.

Get City

After select a country, we can search the city per the input text.

City search endpoint:

  • http://dataservice.accuweather.com/locations/v1/cities/{countryCode}/search

Add getCity function to Home component.

JavaScript
async getCity(searchText: string, countryCode: string): Promise<City> {
    const res = await fetch(`${Constants.locationAPIUrl}/cities/
                ${countryCode}/search?apikey=${Constants.apiKey}&q=${searchText}`);
    const cities = await res.json() as City[];
    if (cities.length > 0)
        return cities[0];
    return {} as City;
}

Get Current Conditions

GetCity returns a City type object, which has location key. With location key, we can call Current Conditions endpoint of Current Conditions API.

Current conditions endpoint:

  • http://dataservice.accuweather.com/currentconditions/v1/{locationKey}

Add getCurrentConditions function to Home component.

JavaScript
async getCurrentConditions(city: City) {
    try {
        const res = await fetch(`${Constants.currentConditionsAPIUrl}/
                                 ${city.Key}?apikey=${Constants.apiKey}`);
        const currentConditions = await res.json() as CurrentCondition[];
        if (currentConditions.length > 0) {
            const weather = new Weather(currentConditions[0], city);
            await this.setStateAsync({
                weather: weather,
                city: city
            } as IState);
        }
    } catch (error) {
        console.log(error);
    }
    return {} as Weather;
}

Now we put getCity and getCurrentConditions together in getWeather.

JavaScript
getWeather = async (e: any, countryCode: string, searchText: string) => {
        e.preventDefault();
        if (!countryCode && !searchText) {
            await this.setStateAsync
                 ({ weather: { error: "Please enter the value." } } as IState);
            return;
        }
        try {
            const city = await this.getCity(searchText, countryCode);
            if (city.Key) {
               await this.getCurrentConditions(city);
            }
        } catch (err) {
            await this.setStateAsync({ weather: { error: err } } as IState);
        }
    };

It appears getWeather has two parameters, countryCode and searchText. These values come from Form component, which means getWeather should be called from Form component. How can we do that?

Just add getWeather to Form component prop, and set it from Home component.

First, we add getWeather to Form prop. In Form.tsx, we change Iprops interface.

JavaScript
interface IProps {
    getWeather: (e: any, country: string, serachText: string) => Promise<void>;
    countries: Country[];
}

Then change handleSubmit:

JavaScript
handleSubmit = async (e: any) => {
        this.props.getWeather(e, this.state.country.ID, this.state.searchText);
    }

Don’t forget set getWeather prop in Home component.

HTML
<div className="form-container">
    <WeatherDetails weather={this.state.weather} />
    <Form getWeather={this.getWeather} countries={this.state.countries} />
</div>

Click “IISExpress” to run again.

Image 15

Select “Australia”, and input “Knoxfield”, then click “Go”, the weather conditions displays as we expected.

Call .NetCore API

Now we move to backend .NetCore API controller. Our target is saving the last selected location to database. And when we load the react app, will retrieve the last input location to get the weather conditions automatically.

All backend code is the same as Global Weather – ASP.NetCore and Angular 7 (part 2). I don’t explain again. Just quick copy all the code. Run “Release 1.0.sql” to create the database.

Basically, we created Cities API, one is http get, the other is http post.

C#
using System.Threading.Tasks;
using GlobalWeather.Services;
using Microsoft.AspNetCore.Mvc;
using Serilog;
using Weather.Persistence.Models;
namespace GlobalWeather.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CitiesController : ControllerBase
    {
        private readonly ICityService _service;
        private readonly ILogger _logger;
        public CitiesController(ICityService service, ILogger logger)
        {
            _service = service;
            _logger = logger;
        }
        // GET api/cities
        [HttpGet]
        public async Task<ActionResult<City>> Get()
        {
            var city = await _service.GetLastAccessedCityAsync();
            return city;
        }
        // POST api/cities
        [HttpPost]
        public async Task Post([FromBody] City city)
        {
            await _service.UpdateLastAccessedCityAsync(city);
        }
    }
}

Now we need call this API from React app.

First, add CityMetaData.ts to “types” folder.

JavaScript
import { City } from './City';
export class CityMetaData {
  public id: string;
  public name: string;
  public countryId: string;
  public constructor(city: City) {
    this.id = city.Key;
    this.name = city.EnglishName;
    this.countryId = city.Country.ID;
  }
}

Then add City API Url to Constants.ts:

JavaScript
static cityAPIUrl = '/api/cities';

Finally, add getLastAccessedCity and updateLastAccessedCity to Home component.

JavaScript
async updateLastAccessedCity(city: City) {
    try {
        const data = new CityMetaData(city);
        await fetch(`${Constants.cityAPIUrl}`, {
            method: 'post',
            body: JSON.stringify(data)
        });
    } catch (error) {
        console.log(error);
    }
}

async getLastAccessedCity(): Promise<City> {
    try {
        const res = await fetch(`${Constants.cityAPIUrl}`);
        const data = await res.json() as CityMetaData;
        return {
            Key: data.id,
            EnglishName: data.name,
            Type: 'City',
            Country: {
                ID: data.countryId,
                EnglishName: ''
            }
        } as City;
    } catch (error) {
        console.log(error);
        return {} as City;
    }
}

In componentDidMount function to call getLastAccessedCity to retrieve last accessed city, if there is a last accessed city, just get weather of this city directly.

JavaScript
async componentDidMount() {
    try {
        const countries = await this.getCountries();
        await this.setStateAsync({ countries: countries } as IState);
        const lastCity = await this.getLastAccessedCity();
        if (lastCity && lastCity.Key) {
            await this.getWeatherAsync(lastCity);
        }
    } catch (error) {
    }
}

Change getWeather function to call updateLastAccessedCity to save the current city.

JavaScript
getWeather = async (e: any, countryCode: string, searchText: string) => {
    e.preventDefault();
    if (!countryCode && !searchText) {
        await this.setStateAsync({ weather: { error: "Please enter the value." } } as IState);
        return;
    }
    try {
        const city = await this.getCities(searchText, countryCode);
        if (city.Key) {
            await this.updateLastAccessedCity(city);
            await this.getWeatherAsync(city);
        }
    } catch (err) {
        await this.setStateAsync({ weather: { error: err } } as IState);
    }
};

Now run the app again, it should remember the city you input last time.

 

How to Use Source Code

  • Install Visual Studio 2019 version 16.3 (or later version), which supports the release of .NET Core 3.0.
  • Install NodeJs the latest version.
  • Download and extract the source code.
  • Go to "weatherclient" folder, run "npm install" from command line.
  • Open GlobalWeatherReact.sln with Visual Studio 2019, click "rebuild all", then start with "IIS Express".

Conclusion

In this article, we talked about client-side React and server-side ASP. NET Core, and REST API. We’re using Create-React-App to build react client and use .NET Core 3.0 to build API. React app and .NetCore API can be integrated seamlessly.

In part 2, we’ll start touching the fun part, React Redux. Without Redux, React only can build something small. But with Redux, React can build an enterprise app.

History

  • 9th October, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)