Skip to main content

React.js 2023 - A (Re)Introduction

TST, Hongkong

Setting up a React project with Vite 4.2

npm create vite@latest

React.js 2023

cd into/dir
npm install && npm run dev

  VITE v4.2.1  ready in 19597 ms

  ➜  Local:   http://localhost:5173/

React.js 2023

To build a browser compatible version of your application run the following command and copy the generated files from the /dist directory into the public dir of your webserver:

npm run build

Creating Components

Create a file HelloThere.jsx in the sub directory components:

import React from "react";

// functional component
function HelloWorld() {
    return <h3>Hello World!</h3>
}

// arrow function
const ILikeArrows = () => {
    return <p>I am Artemis.</p>
}

// class component
class ClassyComponent extends React.Component {
    render() {
        return (
            <>
                <HelloWorld />
                <ILikeArrows />
            </>
        )
    }
}

export default ClassyComponent

This component can be imported into App.jsx and rendered by:

import ClassyComponent from './components/HelloThere'

function App() {

  return (
    <div className="App">
      <div>
        <ClassyComponent />
      </div>
      
      ...

    </div>
  )
}

export default App

Working with Props

// working with props
function Paragraph(props) {
    return (
    <p>In ancient { props.origin }, { props.name } is the { props.profession }. She was heavily identified with Selene, the personification of the Moon, and Hecate, another lunar deity, and was thus regarded as one of the most prominent lunar deities in mythology, alongside the aforementioned two. The goddess { props.equivalent } is her Roman equivalent.</p>
    )
}

function SelfIntroduction() {
    return (
    <>
        <Paragraph
            origin = "Greek mythology"
            name = "Artemis"
            profession = "goddess of the hunt"
            equivalent = "Diana" />
    </>
    )
}

export default SelfIntroduction

Content Projection

// content projection
function Card(props) {
    return (
    <div className="card">
        { props.children }
    </div>
    )
}

function Image() {
    const image = { url: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Diana_of_Versailles.jpg/314px-Diana_of_Versailles.jpg" }

    return (
        
      <Card>
        <img src={ image.url } />
      </Card>
    );
  }

export default Image

Conditional Rendering

// functional component
function ConditionalImage({ goddess }) {

    const diana = { url: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Jean-Fran%C3%A7ois_de_Troy_-_Diane_suprise_par_Act%C3%A9on%2C_1734.jpg/800px-Jean-Fran%C3%A7ois_de_Troy_-_Diane_suprise_par_Act%C3%A9on%2C_1734.jpg" }

    const artemis = { url: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Wall_painting_-_Artemis_and_Kallisto_-_Pompeii_%28VII_12_26%29_-_Napoli_MAN_111441.jpg/505px-Wall_painting_-_Artemis_and_Kallisto_-_Pompeii_%28VII_12_26%29_-_Napoli_MAN_111441.jpg" }

    if (goddess == "diana") {
        return <img src={ diana.url } />
    } else {
        return <img src={ artemis.url } />
    }
}

const ConditionalTitle = (props) => {
    return (
        <div className="title">
        {
            (props.goddess == 'diana')
            ? <h3>The Roman Goddess Diana</h3>
            : <h3>The Greek Goddess Artemis</h3>
        }
        </div>
    )
}


const PaintingCollection = () => {
    return (
        <>
            <h2>Paintings</h2>
            <ConditionalTitle goddess='artemis' />
            <ConditionalImage goddess='artemis' />
            <ConditionalTitle goddess='diana' />
            <ConditionalImage goddess='diana' />
        </>
    )
}

export default PaintingCollection

Loops

const ListItem = (props) => {
    return <li>{props.name}</li>
}

function ListOfAliases() {

    const data = [
        {id: 1, goddess: 'Artemis'},
        {id: 2, goddess: 'Diana'}
    ]

    return (
        <>
            <h3>List of Aliases</h3>
            <ul>
                { data.map(({ id, goddess }) =>
                    <ListItem key={id} name={goddess} />
                )}
            </ul>
        </>
    )
}

export default ListOfAliases

Events

function EventCatcher() {

    const clickHandler = (event) => {
        console.log(event)
    }

    return (
        <>
            <button onClick={clickHandler}>Log Click-Event</button>
        </>
    )
}

export default EventCatcher
function ActionButton({ onClick }) {
    return <button onClick={onClick}>Log Click-Event</button>
}

function EventCatcher() {

    const clickHandler = (event) => {
        console.log(event)
    }

    return <ActionButton onClick={ clickHandler } />
}

export default EventCatcher

State and Reducer Hooks

import { useState, useReducer } from 'react'

const initialState = {count: 0}

function reducer(reducedState, action) {
    switch (action.type) {
        case 'increment':
            return { count: reducedState.count + 1 }
        case 'decrement':
            return { count: reducedState.count - 1 }
        default:
            throw new Error()
    }
}


// props are immutable to be able to modify
// state and redraw ui components use a state hook
function Stateful() {
    const [count, setCount] = useState(0)
    const [state, setState] = useState({ count: 0, goddess: 'Artemis'})

    const handleClick = () => {
        setState({
            ...state,
            count: state.count + 1,
        })
    }

    const [reducedState, dispatch] = useReducer(reducer, initialState)
    
    return (
        <>
            <p>Diana: { count }</p>
            <button onClick={ () => setCount(count +1)}>Like</button>
            <p>{ state.goddess }: { state.count }</p>
            <button onClick={ handleClick }>Like</button>
            <p>God in general: { reducedState.count }</p>
            <button onClick={ () => dispatch({type: 'increment'})}>Like</button>
            <button onClick={ () => dispatch({type: 'decrement'})}>Dislike</button>
        </>
    )
}

export default Stateful

Effects - Lifecycle for Functional Components

No Dependencies

useEffect(() => {
  //Runs on every render
});
import { useState, useEffect } from "react";

function Timer() {
    const [count, setCount] = useState(1000);
  
    useEffect(() => {
      setTimeout(() => {
        setCount((count) => count - 1);
      }, 1000);
    });
  
    return <h1>Countdown: {count}s</h1>;
  }

  export default Timer

An empty Array

Update when component did mount:

useEffect(() => {
  //Runs only on the first render
}, []);

Props or State Values

Update when component did update:

useEffect(() => {
  //Runs on the first render
  //And any time any dependency value changes
}, [prop, state]);
import { useState, useEffect } from "react"

function Counter() {
  const [count, setCount] = useState(0);
  const [calculation, setCalculation] = useState(0);

  useEffect(() => {
    setCalculation(() => Math.pow(count, 2));
  }, [count]); // <- add the count variable here

  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>Add Awesomeness</button>
      <p>Awesomeness: {count}</p>
      <p>Awesomeness²: {calculation}</p>
    </>
  );
}

export default Counter

Unmount Legacy

Update when component did unmount:

useEffect(() => {
  //Runs when component get's destroyed
  return () => console.log('Ciao!')
}, [prop, state]);

Context API - Share Reactive Data among Components

// without context
// pass prop down a chain of child components

function PropDrilling() {
    const [count] = useState(44)

    return <Child count={count} />
}

function Child({ count }) {
    return <GrandChild count={count} />
}

function GrandChild({ count }) {
    return <div>{count}</div>
}
// with context
// write prop to context to make it available to all components

const CountContext = createContext(0)

function PropContext() {
    const [count] = useState(88)

    return (
        <CountContext.Provider value={count}>
            <ChildContext count={count} />
        </CountContext.Provider>
    )
}

function ChildContext() {
    return <GrandChildContext />
}

function GrandChildContext() {
    const count = useContext(CountContext)
    return <div>{count}</div>
}

Error Boundary

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props)
        this.state = { hasError: false }
    }

    // catch error
    static getDerivedStateFromError(error) {
        return { hasError: true }
    }

    componentDidCatch(error, errorInfo) {
        console.log(error, errorInfo)
    }

    render() {
        // there was a problem - provide fallback
        if (this.state.hasError) {
            return <p><em>Safe Fallback instead of a Crash</em></p>
        }
        // everything is fine - continue
        return this.props.children
    }
}


function BreakingComponent() {
    return <p>{ anelusiveprop }</p>
}

function CaughtError() {
    
    return (
        <>
            <ErrorBoundary>
                <BreakingComponent />
            </ErrorBoundary>
        </>
    )
  }

export default CaughtError