Skip to content

React

Usage

  • Use create-react-app
    • > npx create-react-app some-app
  • Add some useful options
    • Typescript --template typescript
      • Flow is alternative to use with React. Provides type checking.
    • Npm –use-npm
      • Default is yarn > npx create-react-app some-app --template typescript --use-npm

Polyfills

  • When dealing with older browsers
    • > npm install react-app-polyfill
1
2
3
// These must be the first lines in src/index.ts
import 'react-app-polyfill/ie9'; // or ie11
import 'react-app-polyfill/stable';

Polyfills support browserlist, only importing what is needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"browserslist": {
    "production": [
        ">0.2%",
        "not dead",
        "not op_mini all"
    ],
    "development": [
        "last 1 chrome version",
        "last 1 firefox version",
        "last 1 safari version"
        ]
}

Extra libs

  • Add support for bootstrap etc the usal npm/webpack way.
  • Import them in index.ts (global import)

> npm i jquery popper.js bootstrap font-awesome > npm i --save-dev @types/jquery @types/bootstrap

1
2
3
4
5
6
7
8
import 'jquery';
import 'popper.js';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css'; 
import 'font-awesome/css/font-awesome.min.css';

import React from 'react';
import ReactDOM from 'react-dom';

Clean initial project

  • Delete initially not needed files from /src.
  • Leave just
    • index.tsx
    • react-app-env.d.ts

Minimal code in index.tsx.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
    <React.StrictMode>
        <div>
            Hello, React World!
        </div>
    </React.StrictMode>,
    document.getElementById('root')
);

MVC

  • React is mostly about View
  • But can do all

    • has local state and Flux for global state
    • Components can contain just logic (Container vs Presentation components)
  • Abstraction is Component, not html vs js

JSX

  • React handles markup syntax named JSX
    • HTML in JavaScript
  • Compiles to JavaScript
  • Optional
1
2
3
4
5
import React from 'react';

function About() {
    return <h1>About</h1>;
}
1
2
3
function About() {
    return React.createElement('h1', null, 'About');
}

babeljs.io

babeljs.io playground

Properties

  • class -> className
  • for -> htmlFor
  • camelCased attributes
    • tabindex -> tabIndex


  • Inline styles
    • {} – js expression in JSX (no statements!)
    • {{…}} js object in js

Since JSX is converted into JS, typos, missing tags, etc. are not allowed!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function About() {
    return (
        <h1 style={{
            color: 'red',
            height: 20
        }}>
            About
        </h1>
    );
}

More about React

  • React implements virtual DOM
  • Synthetic events (delegate vs trigger in aurelia)
  • Isomorphic (serverside rendering)
  • React Native


  • JS in HTML vs HTML in JS
    • <li v-for="user in users"> - logic, data-binding, looping in HTML
    • vs
    • {users.map(createRow)}

Components

  • 4 ways to create React component
    • createClass
    • ES class
    • Function
    • Arrow function

createClass

The original way - obselete

1
2
3
4
5
6
7
var About = React.createClass({
    render: function () {
        return (
            <h1>About</h1>
        );
    }
});

Class component

render() is mandatory to implement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class About extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <h1>About</h1>
        );
    }
}

Function/Arrow function

Function Component

1
2
3
4
5
6
7
function About(props: any) {
    return (
        <h1>
            About
        </h1>
    );
}

Arrow function

1
const About = (props: any) => <h1>About</h1>;

Functional components - why?

  • Easier to understand
  • Avoid ‘this’
  • Less transpiled code
  • Less obscure code
  • Better code completion & intellisense
  • Code smell is easy to see (too many parameters)
  • Easy to test
  • Perform better
  • There is talk, that classes are dropped in future React

When to use class component?

  • Before react 16.8
    • State, Refs, Lifecycle methods – Class
  • After 16.8 – Hooks (released 16.02.2019)
    • componentDidError, getSnapshotBeforeUpdate – Class


  • All the other cases – function components are preferred

Specifics of components

  • Components start with capital letter (even functional).
  • JSX asumes that lower case elements are html, upper case React components
  • return (…); - use parenthesis around JSX when JSX is multiline.
  • JSX can contain only one top level element – use <>… around mutilple upper-level tags.
  • <>… - JSX fragment. Avoids unneccesary divs. (alias to React.Fragment).

Example of components

1
2
3
4
5
import React from 'react';

const Privacy = () => (<h1>Privacy</h1>);

export default Privacy;
1
2
3
4
5
import React from 'react';

const Home = () => (<h1>Home</h1>);

export default Home;

Components example

Functional components Home and Privacy.



Example of components 2

  • Header
  • Html from asp.net

Danger

class -> className

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from 'react';

const Header = () => (
    <header>
        <nav className="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div className="container">
                <a className="navbar-brand" href="/">WebApp</a>
                <button className="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span className="navbar-toggler-icon"></span>
                </button>
                <div className="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul className="navbar-nav flex-grow-1">
                        <li className="nav-item">
                            <a className="nav-link text-dark" href="/">Home</a>
                        </li>
                        <li className="nav-item">
                            <a className="nav-link text-dark" href="/Privacy">Privacy</a>
                        </li>
                        <li className="nav-item">
                            <a className="nav-link text-dark" href="/GpsSessions">Gps Sessions</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
);

export default Header;

App entry point

  • Entry point - index.tsx
  • Template for html page is in /public/index.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import 'jquery';
import 'popper.js';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css'; 
import 'font-awesome/css/font-awesome.min.css';

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

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);

App.tsx

  • Simple routing – relies on server-side catch-all. All urls resolve into same JS app.
  • Router - coming…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';
import Header from './components/shared/Header';
import Privacy from './components/Privacy';
import GpsSessions from './components/GpsSessions';
import Home from './components/Home';

const GetPage = () => {
    const route = window.location.pathname;
    if (route === '/GpsSessions') return <GpsSessions />;
    if (route === '/Privacy') return <Privacy />;
    return <Home />;
}
const App = () => (
    <>
        <Header />
        <div className="container">
            <main role="main" className="pb-3">
                {GetPage()}
            </main>
        </div>
    </>
);

export default App;

Data

  • React is only using one-way data binding!

  • Data is held in two places in every component

    • Props
    • State
  • Props are like html properties – for passing data down to component.

  • Props are immutable – it belongs to parent component.
    • Use parent provided callbacks to update

props

1
2
3
4
5
import React from 'react';

const Privacy = (props: any) => (<h1>Privacy {props.message}</h1>);

export default Privacy;
1
2
3
4
5
6
const GetPage = () => {
    const route = window.location.pathname;
    if (route === '/GpsSessions') return <GpsSessions />;
    if (route === '/Privacy') return <Privacy message="is important!" />;
    return <Home />;
}
1
2
3
4
5
interface PrivacyProps {
    message: string;
}

const Privacy = (props: PrivacyProps) => (<h1>Privacy {props.message}</h1>);

props - default values

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
interface PrivacyProps {
    message: string;
    showIt?: boolean;
}

const Privacy = (props: PrivacyProps = { message: '', showIt: false }) => (
    <h1>
        Privacy {props.showIt ? props.message : 'is disabled'}
    </h1>
);

State (class component)

  • Props – immutable
  • State – mutable
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';

export default class Home extends React.Component {
    state = {
        count: 0
    }

    onAddClicked(): void {
        this.setState({ count: this.state.count + 1 })
    }

    render() {
        return (
            <>
                <h1>Home {this.state.count}</h1>
                <button onClick={() => this.onAddClicked()}>
                    Add one
                </button>
            </>
        );
    }
}

State - class component

  • Declare as property state in class
  • Update with this.setState({ properties to be updated })
  • Access with this.state.property

  • Carefull with this!

Class component props and state

  • Shape of state and props
  • React.Component<P,S>
    • P – properties interface
    • S – state interface
    • Default is {}

Props are accessible as this.props. State as this.state.

LifeCycle - Class components

  • Mounting

    • constructor()
    • static getDerivedStateFromProps()
    • render()
    • componentDidMount()
  • Updating (props or state change)

    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  • Error Handling

    • static getDerivedStateFromError()
    • componentDidCatch()
  • Unmounting

    • componentWillUnmount()

REST client

  • How can I make an AJAX call?
    • You can use any AJAX library you like with React. Some popular ones are Axios, jQuery AJAX, and the browser built-in window.fetch.


  • Where in the component lifecycle should I make an AJAX call?
    • You should populate data with AJAX calls in the componentDidMount lifecycle method (class component). This is so you can use setState to update your component when the data is retrieved.

Axios based example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import Axios from 'axios';
import { IGpsSession } from '../domain/IGpsSession';

export abstract class GpsSessionsApi {
    private static axios = Axios.create(
        {
            baseURL: "https://sportmap.akaver.com/api/v1.0/GpsSessions/",
            headers: {
            common: {
                'Content-Type': 'application/json'
                }
            }
        }
    )

    static async getAll(): Promise<IGpsSession[]> {
        const url = "";
        try {
            const response = await this.axios.get<IGpsSession[]>(url);

            console.log('getAll response', response);
            if (response.status === 200) {
                return response.data;
            }
            return [];
        } catch (error) {
            console.log('error: ', (error as Error).message);
            return [];
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import { IGpsSession } from '../domain/IGpsSession';
import { GpsSessionsApi } from '../services/GpsSessionApi';

interface IProps {}

interface IState {
    items: IGpsSession[]
}

export default class GpsSessions extends React.Component<IProps, IState> {
    state: IState = {
        items: []
    }

    async componentDidMount() {
        const result = await GpsSessionsApi.getAll();
        this.setState({ items: result });
    }

    // ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
rowDisplay(gpsSession: IGpsSession) {
    return (
        <tr key={gpsSession.id}>
            <td>{gpsSession.id}</td>
            <td>{gpsSession.name}</td>
            <td>{gpsSession.description}</td>
            <td>{gpsSession.recordedAt}</td>
            <td>{gpsSession.gpsLocationsCount}</td>
            <td>{gpsSession.userFirstLastName}</td>
        </tr>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
render() {
    return (
        <>
            <h1>Gps Sessions</h1>
            <table className="table table-striped">
                <thead>
                    <tr>
                        <th>Id</th>
                        <th>Name</th>
                        <th>Description</th>
                        <th>Recorded At</th>
                        <th>Locations</th>
                        <th>User</th>
                    </tr>
                </thead>
                <tbody>
                    {this.state.items.map(gpsSession => this.rowDisplay(gpsSession))}
                </tbody>
            </table>
        </>
    );
}

Key

  • Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.
  • The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys.
  • When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort.
  • Keys used within arrays should be unique among their siblings. However they don’t need to be globally unique.

Shared state

  • Pass state down via props (immutable in child component)
  • Include callbacks to update parents state from child component
  • Gets complicated quickly – “Prop Drilling” and callback hell


  • State management – Context, Flux/Redux – coming….

Functional components

  • Props – as parameters
  • State, Lifecycle, … - hooks


Basic hooks - useState - useEffect - useContext

  • Additional Hooks
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

https://reactjs.org/docs/hooks-intro.html

Hooks

Hooks are functions that let “hook into” React state and lifecycle features from function components.

useState

  • useState is the HOOK! Returns array of state variable and update function. Parameter to useState is the initial value of state variable.
  • You can use useState hook several times.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React, { useState } from 'react';

const Home = () => {
    const [count, setCount] = useState(0);

    return (
        <>
            <h1>Home {count}</h1>
            <button onClick={() => setCount(count + 1)}>Add 1</button>
        </>
    );
}

export default Home;

useEffect

  • The Effect Hook, useEffect, adds the ability to perform side effects from a function component.
  • It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes. useEffect runs after every render.

useEffect - no cleanup

useEffect runs after every render. Has access to state/prop variables.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import React, { useState, useEffect } from 'react';

const Home = () => {
    const [count, setCount] = useState(0);

    useEffect(() => {
        document.title = `You clicked ${count} times`;
    });

    return (
        <>
            <h1>Home {count}</h1>
            <button onClick={() => setCount(count + 1)}>Add 1</button>
        </>
    );
}

export default Home;

useEffect – cleanup

1
useEffect(effect: React.EffectCallback, deps?: React.DependencyList | undefined): void
  • If you need to call some function to clean up – return the callback function from the useEffect().
  • Same code that sould run from componentWillUnmount() class lifecycle method.


  • useEffect can be used several times – split unrelated logic.
  • NB! – pass second parameter – array of variables that are used in effect logic – to let React decide should useEffect run. If values are same between renders - effect will not run.
1
2
3
4
5
6
useEffect(() => { 
    API.subscribe();
    return function cleanup() { 
        API.unsubscribe() 
    } 
})

Returned function will run after previous render.

useEffect - cleanup - Axios example

  • Do not update component when it is unloaded
  • Cancel Axios request
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function Example(props) {
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        let mounted = true;

        fetchAPI.then(() => {
            if (mounted) {
                setLoading(false);
            }
        })

        return function cleanup() {
            mounted = false;
        }
    }, [])

    return <div>{loading ? <p>loading...</p> : <p>loaded</p>}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
useEffect(() => {
    const source = axios.CancelToken.source();

    const fetchUsers = async () => {
        try {
            await Axios.get('/users', {
                cancelToken: source.token
            })
            // ...
        } catch (error) {
            if (Axios.isCancel(error)) {

            } else {
                throw error;
            }
        }
    }

    fetchUsers();

    return () => {
        source.cancel();
    }
}, [])

Axios based example - func, hooks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const GpsSessions = () => {
    const [items, setItems] = useState([] as IGpsSession[]);

    useEffect(() => {
        const fetchData = async () => setItems(await GpsSessionsApi.getAll());
        fetchData();
    });

    return (
        <>
            <h1>Gps Sessions</h1>
            <table className="table table-striped">
                <thead>
                    <tr>
                        <th>Id</th>
                        <th>Name</th>
                        <th>Description</th>
                        <th>Recorded At</th>
                        <th>Locations</th>
                        <th>User</th>
                    </tr>
                </thead>
                <tbody>
                    {items.map(gpsSession => <RowDisplay gpsSession={gpsSession} />)}
                </tbody>
            </table>
        </>
    );
}

export default GpsSessions;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React, { useState, useEffect } from 'react';
import { IGpsSession } from '../domain/IGpsSession';
import { GpsSessionsApi } from '../services/GpsSessionApi';

const RowDisplay = (props: { gpsSession: IGpsSession }) => (
    <tr key={props.gpsSession.id}>
        <td>{props.gpsSession.id}</td>
        <td>{props.gpsSession.name}</td>
        <td>{props.gpsSession.description}</td>
        <td>{props.gpsSession.recordedAt}</td>
        <td>{props.gpsSession.gpsLocationsCount}</td>
        <td>{props.gpsSession.userFirstLastName}</td>
    </tr>
)