TypeScript in React
Event and Event Handler types
const MyComponent = () => {
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (
event: React.MouseEvent<HTMLButtonElement>,
) => null
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => null
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => null
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => null
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => null
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => null
return (
<>
<button onClick={handleClick}>Click me</button>
<input onChange={handleInputChange} />
</>
)
}
Type of a React component passed as props
ReactElement
is an object with type, props, and key properties.children
that allownull
:ReactElement | null
JSX.Element
is more generic, equal toReactElement<any, any>
ReactNode
can be aReactElement, string, number, Iterable<ReactNode>, ReactPortal, boolean, null, or undefined
export const ButtonWithIconElement = ({
icon = <DefaultIcon />,
}: {
icon: ReactElement<IconProps> | null
}) => {
const [isHovered, setIsHovered] = useState(false)
const clonedIcon = React.cloneElement(icon, {
// Default props for the passed icon
isHovered: isHovered,
width: 32,
height: 32,
})
return (
<button
onMouseOver={() => setIsHovered(true)}
onMouseOut={() => setIsHovered(false)}
>
{clonedIcon}
</button>
)
}
function SvgComponent(props: any) {
return (
<svg
{/* set the color inside the object */}
fill={props.isHovered ? 'currentColor' : 'red'}
{/* set the color of the line drawn around the object */}
stroke={props.isHovered ? 'currentColor' : 'red'}
width={24}
height={24}
{...props}
/>
)
}
function App() {
return <ButtonWithIconElement icon={<SvgComponent anyProps />} />
}
Get the type of the props
of a component
const MyComponent = ({ name, age }: { name: string; age: number }) => {
return <div>{`My name is ${name} and I am ${age} years old.`}</div>
}
type NewProps = React.ComponentProps<typeof MyComponent> // NewProps is equivalent to MyComponentProps
type ButtonProps = React.ComponentProps<'button'> // == React.ButtonHTMLAttributes<HTMLButtonElement>
Avoid getting the type directly from a component via type NewProps = React.ComponentProps<typeof MyComponent>
, because it will directly depends on the MyComponent
implementation. This violates Dependency Inversion Principle. Instead, we should extract the props
type to a separate type and reuse it
export type MyComponentProps = { name: string; age: number } // reuse this, no need to have `NewProps` type
Default props
, children
, style
, and rest
type ButtonProps = {
children: React.ReactNode // Widest, can be ReactElement, ReactFragment, string...
disabled?: boolean
style?: React.CSSProperties
}
const Button = ({ disabled = false, children, ...rest }: ButtonProps) => (
<button disabled={disabled} style={{ color: 'red' }} {...rest}>
{children}
</button>
)
Hooks
useState
Spec the type specifically to help TS infer the type of the state.
// Enums with `useState`
enum Status {
Pending = 'Đang chờ',
Success = 'Thành công',
}
const [status, setStatus] = useState<Status>(Status.Pending)
<button onClick={() => setStatus(Status.Success)}> Meo </button>
<p>{status}</p> // Thành công
useEffect
Ko dc return gì về ngoại trừ void
/ Destructor
useRef
// If possible, prefer as specific as possible.
// For example, `HTMLDivElement` is better than `HTMLElement` and way better than `Element`.
const divRef = useRef<HTMLDivElement>(null)
return <div ref={divRef}>etc</div>