TypeScript 2.0 in React 19 (2023)
see Workshop fem-react-typescript
WiP
App Scaffolding
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:
JSX.Element;
: Only works if there is a single JSX elementJSX.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;