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;