Skip to main content

TypeScript 2.0 in React 19 (2023)

Shenzhen, China

see Workshop fem-react-typescript

WiP

App Scaffolding

Create-React-App

npx create-react-app typescript2-intro --template typescript

tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

React vs Typed React

jsx

import React from 'react'

const NameBadge = ({name}) => {
    return (
      <section className="badge">
        <header className="badge-header">
            <h1 className="text-5xl">Hello</h1>
            <p>My name is ...</p>
        </header>
        <div className="badge-body">
            <p className="badge-name">{name}</p>
        </div>
        <footer className="badge-footer" />
      </section>
    )
}

const NameCard = () => {
  return (
    <NameBadge name="Gironimo" />
    )
}

export default NameCard

tsx

import React from 'react'

interface NameBadgeProps {
  name: string
  surname?: string // optional
}

const NameBadge = ({ name }: NameBadgeProps): JSX.Element => {
    return (
      <section className="badge">
        <header className="badge-header">
            <h1 className="text-5xl">Hello</h1>
            <p>My name is ...</p>
        </header>
        <div className="badge-body">
            <p className="badge-name">{name}</p>
        </div>
        <footer className="badge-footer" />
      </section>
    )
  }

const NameCard = () => {
  return (
    <NameBadge name="Gironimo" />
    )
}
  
export default NameCard

Children Types

Before we used a string prop and it was easy to assign the correct type to it. But what about children e.g. :

type BoxProps = { children: any /* 👈 Get rid of this! */ };

 const Box = ({ children }: BoxProps) => {
   return (
     <section
       className="m-4"
       style={{ padding: '1em', border: '5px solid purple' }}
     >
       {children}
     </section>
   );
 };
 
 const Application = () => {
   return (
     <main className="m-8">
       <Box>
         Just a string.
         <p>Some HTML that is not nested.</p>
         <Box>
           <h2>Another React component with one child.</h2>
         </Box>
         <Box>
           <h2 className="mb-4">A nested React component with two children.</h2>
           <p>The second child.</p>
         </Box>
       </Box>
     </main>
   );
 };
 
 export default Application;

Typescript prompts us to use one of the following to get rid of the any type:

TypeScript 2.0 in React 19 (2023)

  • JSX.Element;: Only works if there is a single JSX element
  • JSX.Element | JSX.Element[]; For more then one element we can use the array type - but this does not cover the strings we have in our children element.
  • React.ReactNode;: The correct answer that covers us.
type BoxProps = { children: React.ReactNode };

A better more specific alternative is:

type BoxProps = React.PropsWithChildren<{}>

 const Box = ({ children }: BoxProps) => {

  ...

Which also allows us to use style types:

type BoxProps = React.PropsWithChildren<{
  style: React.CSSProperties
}>

 const Box = ({ children, style }: BoxProps) => {
   return (
     <section
       className="m-4"
       style={{ padding: '1em', border: '5px solid purple', ...style }}
     >

  ...

To handle specific JSX components we can use ComponentPropsWithoutRef:

type ButtonProps = React.ComponentPropsWithoutRef<'button>

const Button = ({ children, onclick, type }: ButtonProps) => {
  return (
    <button onClick={onClick} type={type}>
      {children}
    </button>
  )
}

State Type

input type="number" => setDraftCount(e.target.valueAsNumber):

import { useState } from "react"

const Counter = () => {
  const [count, setCount] = useState(0)
  const [draftCount, setDraftCount] = useState(count)
    
  return (
    <section className="flex flex-col items-center w-2/3 gap-8 p-8 bg-white border-4 shadow-lg border-primary-500">
      <h1>Days Since the Last Accident</h1>
      <p className="text-6xl">{count}</p>
      <div className="flex gap-2">
        <button onClick={() => setCount((count) => count -1)}>➖ Decrement</button>
        <button onClick={() => setCount(0)}>🔁 Reset</button>
        <button onClick={() => setCount((count) => count +1)}>➕ Increment</button>
      </div>
      <div>
        <form onSubmit={(e) => {
          e.preventDefault()
          setCount(count + draftCount)
        }}>
          <input
            type="number"
            value={draftCount}
            onChange={(e) => setDraftCount(e.target.valueAsNumber)}/>
          <button type="submit">Add to Counter</button>
        </form>
      </div>
    </section>
  );
};

export default Counter;