August 18, 2022

Blog @ Munaf Sheikh

Latest news from tech-feeds around the world.

React with TypeScript Cheatsheet. An answer to all your React typing… | by Nathan Sebhastian | Nov, 2021


· Table of Contents:
· How to type React props
Creating a type alias for the props
Typing optional props
List of types for React component props
· How to type React function components
· How to type React hooks
Typing useState hook
Typing useEffect and useLayoutEffect hooks
Typing useContext hook
Typing useRef hook
Typing useMemo hook
Typing useCallback hook
Typing custom hooks
· How to type HTML events and forms
· Understanding different typings for React components
When to use each type?
· How to type (extend) HTML elements
ComponentPropsWithoutRef vs [Element]HTMLAttributes
· When to use type vs interface?
· Conclusion

Since React props are used to send transmit data between one React component to another, there are many types that you can use to type React props.

const App = ({ title, score }: { title: string, score: number }) => (
<h1>{title} = {score}</h1>
)

Creating a type alias for the props

Since the convention in React is to write one component in one .js or .jsx file, you can declare a type alias for the component props to make the code easier to read.

type Props = {
title: string,
score: number
}
const App = ({ title, score }: Props) => (
<h1>{title} = {score}</h1>
)

Typing optional props

You can make a prop optional by adding the question mark ? symbol after the prop name.

type Props = {
title?: string,
score: number
}

List of types for React component props

Now that you know how to check the props type, here’s a list of common types that you may want to use in your React application.

type Props = {
// primitive types
title: string,
score: number,
isWinning: boolean
}
type Props = {
title: string[], // an array of string
score: number,
isWinning: boolean
}
type Props = {
priority: "high" | "normal" | "low",
score: 5 | 9 | 10
}
type Props = {
user: {
username: string,
age: number,
isMember: boolean
}

}
type Props = {
user: {
username: string,
age: number,
isMember: boolean
}[] // right here
}
type Props = {
// function that returns nothing
onClick: () => void,
// function accepts a parameter and has return type
onChange: (target: string) => boolean,
// function that takes an event
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void
}
const App = () => {
const [message, setMessage] = useState("")

const onChange = (e: React.FormEvent<HTMLInputElement>): void =>
{
setMessage(e.currentTarget.value);
}

// code omitted for clarity..
}

type Props = {
children: React.ReactNode
}
const App = ({ children }: Props) => (
<div>{children}</div>
)

TypeScript’s Definitely Typed library include the React.FunctionComponent (or React.FC for short) that you can use to type React function components.

type Props = {
title: string
}
const App: React.FC<Props> = ({title}) => {
return (
<h1>{title}</h1>
)
}
type Props = {
title: string
}
const App = ({ title }: Props) => <div>{title}</div>
// App type will be inferred
const App = ({ title }: { title: string }) => <div>{title}</div>

React hooks are supported by @types/react library from version 16.8.

Typing useState hook

The useState value can be inferred from the initial value you set when you call the function.

const App = () => {
const [title, setTitle] = useState("") // type is string
const changeTitle = () => {
setTitle(9) // error: number not assignable to string!
}
}
// title is string or null
const [title, setTitle] = useState<string | null>(null)
// score is number or undefined
const [score, setScore] = useState<number | undefined>(undefined)
interface Member {
username: string,
age?: number
}
const [member, setMember] = useState<Member | null>(null)

Typing useEffect and useLayoutEffect hooks

You don’t need to type the useEffect and useLayoutEffect hooks because they don’t deal with returning values. The cleanup function for the useEffect hook is not considered a value that can be changed either.

Typing useContext hook

The useContext hook type is usually inferred from the initial value you passed into the createContext() function as follows:

const AppContext = createContext({ 
authenticated: true,
lang: 'en',
theme: 'dark'
}
)
const MyComponent = () => {
const appContext = useContext(AppContext) //inferred as an object
return <h1>The current app language is {appContext.lang}</h1>
}
{
authenticated: boolean,
lang: string,
theme: string
}
type Theme = 'light' | 'dark'
const ThemeContext = createContext<Theme>('dark')
const App = () => {
const theme = useContext(ThemeContext)
return <div>The theme is {theme}</div>
}

Typing useRef hook

Based on React documentation, the useRef hook is commonly used to reference an HTML input element as follows:

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
const inputRef = useRef<HTMLInputElement>(null)

Typing useMemo hook

The useMemo hook returns a memoized value, so the type will be inferred from the returned value:

const num = 24// inferred as a number from the returned value belowconst result = useMemo(() => Math.pow(10, num), [num])

Typing useCallback hook

The useCallback hook returns a memoized callback function, so the type will be inferred from the value returned by the callback function:

const num = 9const callbackFn = useCallback(
(num: number) => {
return num * 2 // type inferred as a number
},
[num])

Typing custom hooks

Since custom hooks are functions, you can add explicit types for its parameters while inferring its type from the returned value

function useFriendStatus(friendID: number) {
const [isOnline, setIsOnline] = useState(false);
// code for changing the isOnline state omitted.. return isOnline;
}
const status = useFriendStatus(9) // inferred type boolean
function useCustomHook() {
return ["Hello", false] as const
}

Most HTML events types can be inferred correctly by TypeScript, so you don’t need to explicitly set the type.

const App = () => (
<button onClick={ (e) => console.log("Clicked")}>button</button>
// ^^^ e inferred as React.MouseEvent<HTMLButtonElement, MouseEvent>
)
const App = () => {
const [email, setEmail] = useState("")
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// handle submission here...
alert(`email value: ${email}`)
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Email:
<input
type="email"
name="email"
onChange={(e) => setEmail(e.currentTarget.value)}
// ^^^ onChange inferred as React.ChangeEvent
/>
</label>
</div>
<div>
<input type="Submit" value="Submit" />
</div>
</form>
)
}

Although TypeScript could infer the return type of React function components as you code the components, you may have a project with a linting rule that requires the return type to be explicitly defined.

  • JSX.Element
  • ReactNode
type Key = string | numberinterface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;

}
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
}
}
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

When to use each type?

The ReactNode type is best used for typing a children prop that can receive another React component or JSX elements like this:

const App = ({ children }: { children: React.ReactNode }) => {
return <div>{children}</div>
}
// At index.tsx<App>
<Header/>
<h2>Another title</h2>
</App>
const App = () : React.ReactElement | JSX.Element => {
return <div>hello</div>
}
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}
const App = () : JSX.Element | null => {
return <div>hello</div>
}

Sometimes, you want to create a small, modular component that takes the attributes of a native HTML element as its props.

type ButtonProps = {
children: React.ReactNode
onClick: () => void
}
const Button = ({ children, onClick }: ButtonProps) => {
return <button onClick={onClick}>{children}</button>
}
type ButtonProps = {
children: React.ReactNode
onClick: () => void
disabled: boolean
type: 'button' | 'submit' | 'reset' | undefined
}
type ButtonProps = React.ComponentPropsWithoutRef<"button">const Button = ({ children, onClick, type }: ButtonProps) => {
return (
<button onClick={onClick} type={type}>
{children}
</button>
)
}
type ImgProps = React.ComponentPropsWithoutRef<"img">const Img = ({ src, loading }: ImgProps) => {
return <img src={src} loading={loading} />
}
  • ComponentPropsWithoutRef<'button'> to extend <button> element
  • ComponentPropsWithoutRef<'a'> to extend <a> element
interface ImgProps extends React.ComponentPropsWithoutRef<"img"> {
customProp: string;
}
const Img = ({ src, loading, customProp }: ImgProps) => {
// use the customProp here..
return <img src={src} loading={loading} />;
}
interface headerProps extends React.ComponentPropsWithoutRef<"h1"> {
variant: "primary" | "secondary";
}
const Header = ({ children, variant }: headerProps) => {
return (
<h1 style={{color: variant === "primary" ? "black" : "red" }}>
{children}
</h1>
);
};

ComponentPropsWithoutRef vs [Element]HTMLAttributes

If you have used TypeScript with React before, you may be familiar with the [Element]HTMLAttributes interface from @types/react library that you can use to extend HTML elements as follows:

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>type ImgProps = React.ImgHTMLAttributes<HTMLImageElement>

Both type and interface from TypeScript can be used to define React props, components, and hooks.

interface HtmlAttributes {
disabled: boolean
}
interface ButtonHtmlAttributes extends HtmlAttributes {
type: 'Submit' | 'Button' | null
}
type HtmlAttributes = {
disabled: boolean
}
type ButtonHtmlAttributes = HtmlAttributes & {
type: 'Submit' | 'Button' | null
}
type isLoading = boolean
type Theme = "dark" | "light"
type Lang = "en" | "fr"

Through this tutorial, you’ve learned the most common typings you may need when developing a React-TypeScript application.



Source link