A React and Dotnet Core story

Andreas Sjöberg @ Consid

Agenda

  • Project
    • Tech stack
    • Tech choices
  • React deep dive
  • Angular
  • Vue.js
  • In retrospect
    • Dotnet Core
    • React
    • TypeScript
  • Recap

Project

Project


								let input = fetch('a/bunch/of/excel/sheets');

								let backend = createDotnetCoreApp();
								let frontend = createReactApp();

								input.map(sheet => (
								    backend.implement(sheet.logic);
								    frontend.createPageFor(sheet.tabs);
								));

								return { backend, frontend };
							

  • ~50 Excel sheets
    • From 1 to 20 tabs per sheet
    • Web queries
    • SQL queries
    • FTP
    • Email
    • Cross file reading / writing

Tech stack

Tech stack

  • Backend
    • Dotnet Core
    • EF Core
    • Microsoft.AspNetCore.Identity +
      Microsoft.AspNetCore.Authentication +
      JWT Tokens
    • MediatR
    • SignalR

  • Frontend
    • React (+ Redux)
    • TypeScript
    • Webpack
    • ...and a whole bunch of npm packages

Tech choices

Tech choices

Why Dotnet Core?

Because!

Why React?

  • Previous experience
  • Easy to get started
  • Encourages API backend
  • Large community support

React deep dive

React

JS & JSX

(or TS & TSX)


								const header = <h1>Hello world!</h1>;
							

TS & TSX


							// Helper.ts
							var _ = require("lodash");

							export formatNumber = (n: number, fractions: number = 2) => {
							    if (isNaN(n)) {
							        return n;
							    }
							    return _.round(n, fractions).toLocaleString("sv-SE");
							}
						

							// FormattedCell.tsx
							class FormattedCell extends React.Component<Props, State> {
							    public render() {
							        let { value } = this.props;
							        let className = value < 0 'table-danger' : 'table-success';
							
							        return <td className={className}>
							                   {formatNumber(value)}
							               </td>;
							    }
							}
						

Props and State


							// FormattedCell.tsx
							class FormattedCell extends React.Component<Props, State> {
							    public render() {
							        let { value } = this.props;
							        let className = value < 0 'table-danger' : 'table-success';
							
							        return <td className={className}>
							                   {formatNumber(value)}
							               </td>;
							    }
							}
						

							// ItemsList.tsx
							type Props = {
							    date: Date;
							};

							type State = {
							    isLoading: boolean;
							    error: boolean;
							    items: Item[];
							};

							interface Item {
							    id: number;
							    name: string;
							    value: number;
							}
							// ...
						

							// ...
							class ItemsList extends React.Component<Props, State> {
							    // ...
							    public render() {
							        let { isLoading, error, items } = this.state;
							
							        return
							            <div>
							                <h1>Items list</h1>
							                <ErrorBox error={error} />
							                <table>
							                    <thead>...</thead>
							                    <tbody>
							                        {items.map(item => 
							                            <tr key={item.id}>
							                                <th>{item.name}</th>
							                                <FormattedCell value={item.value} />
							                            </tr>
							                        )}
							                    </tbody>
							                </table>
							                <Loader isLoading={isLoading} />
							            </div>;
							    }
							}
						

Creating a component


							class Loader extends React.Component<Props, State> {
							    public render() {
							        let { isLoading } = this.props;
							        return isLoading ? (
							            <div className="loader" />
							        ) : null;
							    }
							}
						

							<div>
							    <Loader isLoading={true} />
							</div>
						

Component lifecycle events

  • constructor(props)
  • render()
  • componentDidMount()
  • componentDidUpdate()
  • componentWillUnmount()

Adding a react component to an existing page or site


							// index.tsx
							
							import React from 'react';
							import ReactDOM from 'react-dom';
							import App from './App';

							ReactDOM.render(<App />, document.getElementById('my-react-app-div'));
						

							<!-- ... existing HTML ... -->

							<script src="my-react-bundle.js"></script>

							<!-- ... existing HTML ... -->

							<div id="my-react-app-div"></div>

							<!-- ... existing HTML ... -->
						

Virtual DOM

Conditional rendering


							class Loader extends React.Component<Props, State> {
							    public render() {
							        let { isLoading } = this.props;
							        return isLoading ? (
							            <div className="loader" />
							        ) : null;
							    }
							}
						

Lists and keys


							<ul>
								{items.map(item =>
									<li key={item.id}>
										{item.text}
									</li>
								)};
							</ul>
						

Code

Angular

heroes.component.html

							<h2>{{hero.name | uppercase}} Details</h2>
							<div><span>id: </span>{{hero.id}}</div>
							<div>
								<label>name:
									<input [(ngModel)]="hero.name" placeholder="name"/>
								</label>
							</div>
						
heroes.component.ts

							import { Component, OnInit } from '@angular/core';
							import { Hero } from '../hero';
							
							@Component({
								selector: 'app-heroes',
								templateUrl: './heroes.component.html',
								styleUrls: ['./heroes.component.css']
							})
							export class HeroesComponent implements OnInit {
								hero: Hero = {
									id: 1,
									name: 'Windstorm'
								};
							
								constructor() { }
							
								ngOnInit() { }
							}
						
app.module.ts

							import { BrowserModule } from '@angular/platform-browser';
							import { NgModule } from '@angular/core';
							import { FormsModule } from '@angular/forms';
							
							import { AppComponent } from './app.component';
							import { HeroesComponent } from './heroes/heroes.component';
							
							@NgModule({
								declarations: [
									AppComponent,
									HeroesComponent
								],
								imports: [
									BrowserModule,
									FormsModule
								],
								providers: [],
								bootstrap: [AppComponent]
							})
							export class AppModule { }
						
app.component.ts

							import { Component } from '@angular/core';

							@Component({
								selector: 'app-root',
								templateUrl: './app.component.html',
								styleUrls: ['./app.component.css']
							})
							export class AppComponent {
								title = 'Tour of Heroes';
							}
						
app.component.html

							<h1>{{title}}</h1>
							<app-heroes></app-heroes>
						
hero.ts

							export class Hero {
								id: number;
								name: string;
							}
						

Vue.js


							<div id="app-4">
								<ol>
									<li v-for="todo in todos">
										{{ todo.text }}
									</li>
								</ol>
							</div>
						

							var app4 = new Vue({
								el: '#app-4',
								data: {
									todos: [
										{ text: 'Buy milk' },
										{ text: 'Watch videos' },
										{ text: 'Code' }
									]
								}
							})
						

In retrospect

Dotnet Core

Pros


								services.AddAuthorization()
								        .AddMediatR()
								        .AddSignalR();

								services.AddTransient<JwtService>();
							

Cons

  • Update versions 1.0 -> 1.1 -> 2.0 -> 2.1 -> 2.2

React

Pros

  • API backend
  • Separated UI and logic
  • Hot reload
  • Component friendly
  • Easy setup
  • Good maintenance

Cons

  • Bundle sizes?

TypeScript

Pros


								type State = {
								    isLoading: boolean;
								    error: boolean;
								    items: ResponseItem[];
								    date: Date;
								};

								interface ResponseItem {
								    id: number;
								    name: string;
								    value: number;
								}
							

Cons

  • @types npm packages

Recap

Recap

  • Dotnet Core
  • React
  • TypeScript
  • Redux 🤷‍

The end

Andreas Sjöberg @ Consid