Skip to content

React

Usage

Plain react usage is not anymore recommended (although possible).

You can use React without a framework, however most apps and sites eventually build solutions to common problems such as code-splitting, routing, data fetching, and generating HTML. These problems are common to all UI libraries, not just React.

By starting with a framework, you can get started with React quickly, and avoid essentially building your own framework later.

Options are:

  • next.js
  • Remix
  • Gatsby
  • Expo

Next.js

  • Use create-next-app
    • > npx create-next-app@latest
  • choose your options
    • Tailwind or bootstrap?
    • ESLint?
    • Pages router (yes)

Since asp.net is bootstrap based, use that? Cam just copy-paste html from asp.net mvc to js.

Clean up initial code

  • empty global.css and page.module.css
  • clean out page.tsx
1
2
3
4
5
6
7
export default function Home() {
  return (
    <main>
      <h1>Hello, World!</h1>
    </main>
  )
}

Add bootstrap support

> npm install bootstrap

Modify layout.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import type { Metadata } from "next";
import 'bootstrap/dist/css/bootstrap.css';
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
};

export default function RootLayout({
    children,
}: Readonly<{children: React.ReactNode;}>) {
    return (
        <html lang="en">
            <body className={inter.className}>{children}</body>
        </html>
    );
}

Modify page.tsx to include bootstrap style class.

1
2
3
4
5
6
7
export default function Home() {
  return (
    <main>
      <h1 className="text-danger">Hello Bootstrap</h1>
    </main>
  )
}

Add support for bootstrap js.
Create src/components/BootstrapActivation.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"use client"

import { useEffect } from 'react';

function BootstrapActivation() {
    useEffect(() => {
        require('bootstrap/dist/js/bootstrap.bundle.min.js');
    }, []);

    return null;
}

export default BootstrapActivation;

Modify layout.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import type { Metadata } from "next";
import 'bootstrap/dist/css/bootstrap.css';
import { Inter } from "next/font/google";
import "./globals.css";
import BootstrapActivation from '@/components/BootstrapActivation';

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
};

export default function RootLayout({children}: Readonly<{children: React.ReactNode;}>) {
    return (
        <html lang="en">
            <body className={inter.className}>
                {children}
                <BootstrapActivation />
            </body>
        </html>
    );
}

Include bootstrap js code in page.tsx for testing

 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
32
33
34
35
36
export default function Home() {
    return (
        <main>
            <div className="text-center mt-4 col-md-6 mx-auto">
                <h1 className="text-danger">Hello Bootstrap</h1>

                <div className="accordion" id="accordionExample">
                    <div className="accordion-item">
                        <h2 className="accordion-header" id="headingOne">
                            <button className="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
                                Accordion Item #1
                            </button>
                        </h2>
                        <div id="collapseOne" className="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
                            <div className="accordion-body">
                                <strong>This is the first accordion body.</strong>
                            </div>
                        </div>
                    </div>
                    <div className="accordion-item">
                        <h2 className="accordion-header" id="headingTwo">
                            <button className="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
                                Accordion Item #2
                            </button>
                        </h2>
                        <div id="collapseTwo" className="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
                            <div className="accordion-body">
                                <strong>This is the second accordion body.</strong>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </main>
    );
}

MVC

  • React is mostly about View
  • But can do all

    • has local state and several global state libraries
    • 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
1
2
3
function About() {
    return <h1>About</h1>;
}
1
2
3
4
import React from 'react';
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
  • 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;

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>
)