Skip to content

Latest commit

 

History

History
1016 lines (701 loc) · 15 KB

File metadata and controls

1016 lines (701 loc) · 15 KB

React + TypeScript***

*** just a little bit


Martin van Dam

Frontend Engineer @ Port of Rotterdam @MrtnvDam
[email protected]

Corneel Eijsbouts

Software Developer @ Min. VWS  
[email protected]

Expectation management

  • Interactive + live coding exercises
  • React
  • TypeScript (just a little bit)
  • TSX
  • States, Effects, Props
  • Component composition
  • Containers vs. Presentational components

⚠️ Disclaimer

  • Hooks instead of classic state 🏴‍☠️
  • TSX 💯
  • StackBlitz 🚀
  • Contains some inline styling 🙀

What are we going to build?

☣️ ➡️

https://code-star.github.io/typescript-react-101


⚠️ But first: What is React?

Anyone?


React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”.


  • Alternative for Angular / Vue / etc etc
  • Basic and compact
  • Scales for bigger applications as well
  • Works nicely with State Management solutions (Redux, MobX, GraphQL)

What is a component?

const App = () => (
  <div>My App</div>
)

Use your component

<App />

  • XML-like syntax
  • Like a regular function
  • Returns JSX / TSX

What is JSX?

  • Syntax extention to JavaScript
  • Like a template language, but for client side
  • Produces React Nodes

Markup + JavaScript

Use regular JavaScript between { ... }:

const myVariable = "Some value"
const App = () => (
  <div>
    My variable uppercase: {myVariable.toUpperCase()}
  </div>
)

Native element properties

const App = () => (
  <div
    style={{ textTransform: "uppercase" }}
    className="myClassName">
      ...
  </div>
)

Native element properties

const buttonDisabled = true
const App = () => (
  <input
    type="button"
    value={"Click me!"} // <- both notations can be used
    disabled={buttonDisabled}
  />
)

Styling using CSS stylesheet

Using normal CSS

import classes from "./styles.css"

const App = () => (
  <div className={classes.myClassName}>...</div>
)

Styling using inline styles

Using CSS-in-JS

const App = () => (
  <div
    style={{
      fontSize: "18px",
      backgroundColor: "green",
    }}
  >
    ...
  </div>
)

(not recommended, but easy to start with)


Lets code!

Start your project here! ️

️➡️ https://stackblitz.com/fork/react-ts

OR

️➡️ https://codesandbox.io/s/

...

Cheatsheets: Javascript, TypeScript


Exercise 1

In App.tsx:

Create static HTML / JSX elements for:

  • Time (mm:ss)
  • Start / stop button


Exercise 1 - Hints

<div style={{ backgroundColor: 'blue' }}></div>
<button>My button</button>


Exercise 1 - Result

<div>
  <div style={{ fontWeight: "bold" }}>00:00</div>
  <div>
    <button style={{ backgroundColor: "hotpink" }}>
      Start
    </button>
  </div>
</div>

Conditionals

const isEnabled = true

const App = () => (
  <div>
    {isEnabled ? "Enabled" : "Disabled"}
  </div>
)

Iterations

const animals = ["cat", "dog"]
const App = () => (
  <div>
    {animals.map((animal, index) => (
      <div key={index}>{animal}</div>
    ))}
  </div>
)

Additional JSX/TSX benefits

  • Prevents injection attacks
  • Composable using children
  • You can use TypeScript instead of JavaScript

JSX + TypeScript = TSX

const animals: string[] = ["cat", "dog"]
const App = () => (
  <div>
    {animals.map((animal: string, index: number) => (
      <div key={index}>{animal}</div>
    ))}
  </div>
)

Benefits of TSX

  • Typesafety inside your views
  • Definitions of your props and state
  • No need for PropTypes

State

  • Store information inside your component
  • Useful for forms, loading state and much more

State example 0

import { useState } from "react"

const App: FC = () => {
  const [username, setUsername] = useState("")
  return (
    <div>
      <input
        type={"text"}
        value={username}
        onChange={(event) => setUsername(event.target.value)}
      />
    </div>
  )
}

State example 1

import { useState } from "react"

const App = () => {
  const [isLoading, setIsLoading] = useState(true)

  setTimeout(() => setIsLoading(false), 5000)

  if (isLoading) {
    return <div>Loading...</div>
  }
  return <div>Loading complete!</div>
}

State example 2

import { useState } from "react"

const App = () => {
  const [user, setUser] = useState<User | false>(false)

  fetchUser().then((user: User) => setUser(user))

  if (!user) {
    return <div>Loading user...</div>
  }
  return <div>Username: {user.name}</div>
}

Exercise 2

Create getters and setters for:

  • The elapsed time: number
  • The start time: number
  • The stopwatch running state: boolean
const [myState, setMyState] = useState( ... )

Exercise 2 - Result

const [elapsedTime, setElapsedTime] = useState(0)
const [startMoment, setStartMoment] = useState(0)
const [timerRunning, setTimerRunning] = useState(false)

useState getter

const App = () => {
  const [buttonDisabled, setButtonDisabled] = useState(false)

  return <button disabled={buttonDisabled}>Click me!</button>
}

useState setter

const App = () => {
  const [buttonDisabled, setButtonDisabled] = useState(false)

  return (
    <button
      onClick={() => setButtonDisabled(true)}
      disabled={buttonDisabled}>
        Click me!
    </button>
  )
}

Exercise 3

Use the created getters and setters for:

  • When the start/stop button is clicked
  • The text of the start/stop button should depend on the state

Exercise 3 - Result

<button onClick={() => setIsRunning(!isRunning)}>
  {isRunning ? "Stop" : "Start"}
</button>

Effects

  • Perform side effects
  • Use the component lifecycle

On mount

import { useEffect } from 'react'

...

useEffect(() => {
  alert('The component is mounted!')
}, [])

...

On props / state update

(not recommended)

...

useEffect(() => {
  alert('The props and/or state has changed!')
})

...

On specific prop / state update

...

const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
  alert('The isLoading state has changed!')
}, [isLoading])

...

What happens here?

let timer = 0

const App = () => {
  useEffect(() => {
    timer = setInterval(() => console.log("tick"), 1000)
  }, [])

  return <div>...</div>
}

Exercise 4

When the stopwatch button is clicked:

  • Start: store the current moment as startMoment
  • Stop: display the elapsed time

Hint: use Date.now() inside useEffect


Exercise 4 - Result

Update the states every time timerRunning updates

const App = () => {
  const [timerRunning, setTimerRunning] = useState(false)
  const [startMoment, setStartMoment] = useState(0)
  const [elapsedTime, setElapsedTime] = useState(0)

  useEffect(() => {
    if (timerRunning) {
      setStartMoment(Date.now())
    } else {
      setElapsedTime(Date.now() - startMoment)
    }
  }, [timerRunning])
  ...
}

Exercise 4 - Actually...

Update the states every time timerRunning updates

const App = () => {
  //initial state undefined
  const [timerRunning, setTimerRunning] = useState()
  const [startMoment, setStartMoment] = useState(0)
  const [elapsedTime, setElapsedTime] = useState(0)

  useEffect(() => {
    // guard clause
    if (typeof timerRunning === 'undefined') {
      return;
    }
    if (timerRunning) {
      setStartMoment(Date.now())
    } else {
      setElapsedTime(Date.now() - startMoment)
    }
  }, [timerRunning])
  ...
}

Exercise 5

  • Convert the milliseconds in state to minutes and seconds and display it on the screen

Hints:

  • Math.floor((ms / 1000) / 60)
  • Math.floor((ms / 1000) % 60)


Props

Pass information to your components:

<App title="App title" />

Or using a variable:

const appTitle = 'App title'

<App title={appTitle} />

Access Props inside the receiving component:

<App title="Hi" />
const App = (props) => (
  <div>{props.title}</div>
)

But wait... what is inside my props...?

I'm missing types here!


Typesafe Props

Define the type of your props:

type AppProps = {
  title: string
  isloading: boolean
}

And use inside your component:

const App: FC<AppProps> = (props) => (
  <div>
    <div>{props.title}</div>
    <div>{props.username}</div> /* Error! */
  </div>
)

FC === FunctionComponent

More about this later!


Callback props

type AppProps = {
  onLogin: () => void
}
const App: FC<AppProps> = (props) => (
  <div>
    <button onClick={props.onLogin}>Login!</button>
  </div>
)

Define the function and pass through

const onLogin = () => {
  // some magic happens
}
<App onLogin={onLogin} />

Typesafe Props

type AppProps = {
  title: string
}
<App />

What will happen here?



Type '{}' is not assignable to type 'Readonly<AppProps>'.
  Property 'title' is missing in type '{}'.



Children

type Props = {
  // before React v18 this was included in the `FC` type
  // now we need to be explicit about it
  children?: React.ReactNode
}

const Page: FC<Props> = (props) => (
  <div>{props.children}</div>
)
<Page>
  <div>I'm the child!</div>
</Page>

Composition example

<App>
  <Header>
    <Navigation />
  </Header>
  <Content>
    <SearchForm />
  </Content>
</App>

Exercise 6

  • Create new file Button.tsx
  • Create a Button component and put the button code into it
  • Import the button component in the main file
  • Make it reusable

Exercise 6 - Example

// Button.tsx
type ButtonProps = {
  label: string
  ...
}
export const Button = props => (
  <button>{props.label}</button>
)
import { Button } from "./button"

Exercise 7: Bonus

  1. Create a new button to start a new lap
  2. When the New lap button is clicked:
    • Use lastLapMoment to populate laps

Hint: copy the existing array and add a value to it:

const [lastLapMoment, setLastLapMoment] = useState(0)
const [laps, setLaps] = useState<number[]>([])
setLaps([...laps, Date.now() - lastLapMoment])

Hint: use setLastLapMoment on start as well


Exercise 8: Bonus

  • Show all the laps on the screen

Hint: use .map() here.

{
  laps.map((lap, index) => <div>...</div>)
}

Exercise 9: BONUS

When the stopwatch is running:

  • Update the elapsed time every second

Hint: use setInterval inside useEffect here


Exercise 9 - Example

Update the elapsed time every second

let timer = 0 // <-- IMPORTANT!

const App = () => {
  const [timerRunning, setTimerRunning] = useState(false)
  // define more state here...

  useEffect(() => {
    if (timerRunning) {
      timer = setInterval(() => {
        // use your setter here...
      }, 1000)
    }
  }, [timerRunning])
  ...
}

Exercise 10: BONUS

When the stopwatch stop button is clicked:

  • Stop the timer
  • Make sure that the button text reflects the state

Hint: use clearInterval inside the same useEffect here


Exercise 10 - Example

When the stopwatch stop button is clicked:

let timer = 0
const App = () => {
  ...
  useEffect(() => {
    if (timerRunning) {
      ...
    } else {
      clearInterval(timer)
    }
  }, [timerRunning])
  ...
}

Container vs. Presentational Component

Or: smart vs. dumb

Or fat vs. skinny

Or statefull vs. pure

Or screens vs. components

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0


Container Component

  • Fetches data
  • Keeps track of state
  • Possibly connected to a State Store (Redux / MobX)
  • Passes all the data to its children
  • Does NOT contain styles

Presentational Component

  • Renders UI
  • Contains styling
  • Easily testable
  • As dumb as possible

Possible end result

➡️ Click


Function vs. Class

Props

const App: FC<Props> = (props) => <div>{props.title}</div>
class App extends React.Component<Props> {
  public render() {
    return <div>{this.props.title}</div>
  }
}

Function Component

On mount & State

const App: FC<Props> = (props) => {
  const [user, setUser] = useState(false)

  useEffect(() => {
    fetchUser().then((user) => setUser(user))
  }, [])
}

Class Component

On mount & State

class App extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = {
      user: false
    }
  }

  public async componentDidMount() {
    const user = await fetchUser()
    this.setState({
      user
    })
  }
}

React Hooks

  • Recommended approach
  • Class Components will be removed from the core

Wrap-up

  • React + TypeScript
  • TSX
  • States, Effects, Props
  • Component composition
  • Containers vs. Presentationals

Resources


🙌 Thanks 🙌

Questions anyone?


Bonus exercise 1

Refactor your code to use a Container Component which handles all the state and passes it through presentational components


Bonus exercise 2

Try to refactor a FunctionComponent into a ClassComponent.