Here are notes of begginners tutorial . Why? TypeScript is a way to describe a shape of an object It provides an additional layer of documentation Gives autocompletion in text editor Makes maintenance of large code easier Static types checking shows potential bugs It is initial investment which pays off on a long run If we hover over a variable we can see type inference TS notifies about missing arguments Create typescript Import typescript into existing project with npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest OR Create a new react project with typescript with npx create-react-app my-app --template typescript TypeScript files should have .tsx extension. Basic types
function myFunc(
arg1: string,
arg2: number,
arg3: boolean,
arg4: 1 | 2 | string | number | boolean | bigint | null | symbol | undefined | any, // OR
arg5: {
val1: string
val2: number
}, // object
arg6: string[], // array of strings
arg7: {
first: string
last: string
}[], // arr of objects
arg8: 'loading' | 'success' | 'error', // union of specific strings
arg9: () => void, // function returning undefined
arg10?: string, // optional
): void {
console.log(arguments)
}
const someFunc = () => console.log('hi')
myFunc(
'str1',
666,
true,
1,
{ val1: 'hi', val2: 5 },
['a', 'b'],
[{ first: 'John', last: 'Dow' }, { first: 'Jane', last: 'Blake' }],
'error',
someFunc,
'optional string',
)
Array of objects
type MenuType = {
id: string
name: string
link?: any
func?: () => void,
}
type Props = {
navStructure: MenuType[],
id: string,
}
Object of objects
type ObjectOfObjectsType = {
[key: string]: {
name: string
age: number
}
}
Record
const obj: Record<string, number> = {
'key 1': 1,
'key 2': 2
'key 3': 3
}
Rest of props
type Props = {
children?: React.ReactNode
httpStatus?: 'loading' | 'error' | 'success' | ''
[x:string]: any // all other ...props
}
Function returning undefined
type Props = {
children?: React.ReactNode
color?: string
onSlideIn?: (value: string) => void
onSlideOut?: () => void
otherFunc: (() => void) | null
}
Comments for types Interface & Type To set types separately we can use interface or type which are basically same things Interface ,
interface propsTypes1 {
name: string;
lastName?: string;
likesNum: string | number;
}
function Msg1(props: propsTypes1): JSX.Element {
return (
<div>
Hello {props.name}
{props.lastName && ' ' + props.lastName}, you have received{' '}
{props.likesNum} likes.
</div>
);
}
<Msg1 name="John" likesNum={5} />
Type Types can be assigned also via type , which is preferable.
type propTypes2 = {
name: string
company?: string
likesNum: string | number
isLogged: boolean
spouseName: {
name: string
lastName: string
}
cars: string[]
}
function Msg2(props: propTypes2): JSX.Element {
return (
<>
Hello {props.name} {props.company && `from ${props.company}`}
{props.isLogged && `, you have received ${props.likesNum} likes.`} We wish
you and {props.spouseName.name} {props.spouseName.lastName} a good day.
Your following cars have unpaid fine tickets:{' '}
{props.cars.map((car, i) => {
const ending = i !== props.cars.length - 1 ? ' & ' : ''
return car + ending
})}
</>
)
}
<Msg2
isLogged={true}
name="John"
likesNum={'five'}
spouseName={{ name: 'Jane', lastName: 'Dow' }}
cars={['bmw', 'vw', 'audi']}
/>
Children in a component
function ComponentWithChildren(props: {children: string}) {
return <h2> {props.children} </h2>
}
<ComponentWithChildren>hi</ComponentWithChildren>
hi React node To pass react component as an argument
function ComponentWithReactComponentAsChild(props: {children: React.ReactNode}) {
return <h2> {props.children} </h2>
}
<ComponentWithReactComponentAsChild>
<ComponentWithChildren>hi</ComponentWithChildren>
</ComponentWithReactComponentAsChild>
hi Click event onClick event handler without return & without event object
function Button(props: { handleClick: () => void }) {
return (
<button onClick={props.handleClick}> Click </button>
)
}
<Button handleClick={() => { console.log('Button clicked') }} />
onClick event handler with event object
function Button2(props: { handleClick: (e: React.MouseEvent<HTMLButtonElement>) => void }) {
return (
<button onClick={props.handleClick}> Click </button>
)
}
<Button2 handleClick={(e) => { console.log('Button clicked', e) }} />
onClick event handler with additional parameter A bit strange example
// type for event with id parameter
function Button3(props: { handleClick: (e: React.MouseEvent<HTMLButtonElement>, id: number) => void }) {
return (
<button onClick={e => props.handleClick(e, 1)}>Click</button>
)
}
<Button3 handleClick={(e, id) => { console.log('Button clicked', e, id) }} />
onChange event handler as a prop
type InputProps = {
value: string
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function Input(props: InputProps) {
return <input type="text" value={props.value} onChange={props.handleChange} />
}
<Input value='' handleChange={(e) => console.log(e)}/>
onChange event handler defined within component
type InputProps1 = {
value: string
}
function Input1(props: InputProps1) {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => console.log(e)
return <input type="text" value={props.value} onChange={handleChange} />
}
<Input1 value=''/>
Styles as props
type ContainerProps = {
styles: React.CSSProperties
}
function SomeCmpt(props: ContainerProps) {
return (
<div style={props.styles}> Some text </div>
)
}
<SomeCmpt styles={{ border: '1px solid black', padding: '1rem' }} />
Specific css prop
const [visibility, setVisibility] = useState<CSSProperties['visibility']>('visible')
Props destructuring
type SomeProps = {
styles: React.CSSProperties
}
function Cmpt({ styles }: SomeProps) {
return (
<div style={styles}> Some text </div>
)
}
<Cmpt styles={{ border: '1px solid black', padding: '1rem' }} />
Exporting types
// types.ts
export type ExportedTypes = {
styles: React.CSSProperties
}
// main.tsx
import { ExportedTypes } from './types'
function Cmpt2({ styles }: ExportedTypes) {
return (
<div style={styles}> Some text </div>
)
}
<Cmpt2 styles={{ border: '1px solid black', padding: '1rem' }} />
Re-use types
type Name = {
first: string
last: string
}
type PersonProp = {
name: Name
}
function Person(props: PersonProp) {
return <h2>{props.name.first} {props.name.last}</h2>
}
<Person name={{ first: 'John', last: 'Dow' }} />
Array of other types
type Nameee = {
first: string
last: string
}
type PersonsListProps = {
names: Nameee[]
}
function PersonList(props: PersonsListProps) {
return (
<div>
{props.names.map(name => {
return (
<Hs key={name.first}>
{name.first} {name.last}
</Hs>
)
})}
</div>
)
}
const namesArr = [
{ first: 'John', last: 'Dow' },
{ first: 'Jane', last: 'Blake' },
{ first: 'John', last: 'Dow' },
];
<PersonList names={namesArr} />
useState TypeScript automatically defines a type for useState(initVal) variable based on initial value provided But if the type is unknown beforehand, more complex, or may change in future then we may specify it in angled brackets.
type AuthType = {
name: string
mail: string
}
function User() {
const [userState, setUserState] = React.useState<null | AuthType>(null)
const handleLogin = () => setUserState({ name: 'John', mail: 'john@mail.com' })
const handleLogout = () => setUserState(null)
return <div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleLogout}>Logout</button>
<div>User name is {userState?.name}</div>
<div>User name is {userState?.mail}</div>
</div>
}
<User />
Note that VSCode automatically put optional chaining because we provided the type as null useState type assertion If we are sure that state value will be set to not a null we may define initial state as an empty object with a certain type For example we set a value as soon as a component mounts with useEffect hook
type AuthType2 = {
name: string
mail: string
}
function User2() {
const [userState, setUserState] = React.useState<AuthType>({} as AuthType2)
const handleLogin = () => setUserState({ name: 'John', mail: 'john@mail.com' })
return <div>
<button onClick={handleLogin}>Login</button>
<div>User name is {userState.name}</div>
<div>User name is {userState.mail}</div>
</div>
}
<User2 />
Note that no optional chaining anymore. useReducer
type CounterStateType = {
count: number
}
type UpdateActionType = {
type: 'increment' | 'decrement' | 'reset'
payload?: number
}
const initialState = { count: 0 }
function reducer(state: CounterStateType, action: UpdateActionType) {
switch (action.type) {
case 'increment': return { count: state.count + (action.payload || 0) }
case 'decrement': return { count: state.count - (action.payload || 0) }
case 'reset': return initialState
default: return state
}
}
const Counter = () => {
const [state, dispatch] = React.useReducer(reducer, initialState)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment', payload: 10 })}> Increment 10 </button>
<button onClick={() => dispatch({ type: 'decrement', payload: 10 })}> Decrement 10 </button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
)
}
<Counter />
useReducer (better approach)
type CounterState2 = {
count: number
}
type UpdateAction2 = {
type: 'increment' | 'decrement'
payload: number
}
type ResetAction2 = {
type: 'reset'
}
type CounterAction2 = UpdateAction2 | ResetAction2
const initialState2 = { count: 0 }
function reducer2(state: CounterState2, action: CounterAction2) {
switch (action.type) {
case 'increment': return { count: state.count + action.payload }
case 'decrement': return { count: state.count - action.payload }
case 'reset': return initialState2
default: return state
}
}
const Counter2 = () => {
const [state, dispatch] = React.useReducer(reducer2, initialState2)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment', payload: 10 })}> Increment 10 </button>
<button onClick={() => dispatch({ type: 'decrement', payload: 10 })}> Decrement 10 </button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
)
}
<Counter2 />
useContext
// UserContext.tsx
import React from 'react'
type AuthUser = {
name: string
email: string
}
type UserContextType = {
user: AuthUser | null
setUser: React.Dispatch<React.SetStateAction<AuthUser | null>>
}
type UserContextProviderProps = {
children: React.ReactNode
}
export const UserContext = React.createContext({} as UserContextType)
export const UserContextProvider = ({ children }: UserContextProviderProps) => {
const [user, setUser] = React.useState<AuthUser | null>(null)
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
)
}
// main.tsx
import React from 'react'
import { UserContext, UserContextProvider } from './UserContext'
export const Userrr = () => {
const userContext = React.useContext(UserContext)
const handleLogin = () => userContext.setUser({ name: 'John', email: 'john@mail.com' })
const handleLogout = () => userContext.setUser(null)
return (
<div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleLogout}>Logout</button>
<div>User name is {userContext.user?.name}</div>
<div>User email is {userContext.user?.email}</div>
</div>
)
}
<UserContextProvider>
<Userrr />
</UserContextProvider>
useRef as DOM element
const DomRef = () => {
const inputRef = React.useRef<HTMLInputElement>(null!)
React.useEffect(() => inputRef.current.focus(), [])
return <input type='text' ref={inputRef} />
}
<DomRef />
Note that we put an exclamation mark after null type null! to tell TS that we are sure that variable will not be null and thus we avoid optional chaining inputRef.current?.focus() useRef with ElementRef Same as above, but no need to remember html element types, but just tag names
import { useRef, type ElementRef } from 'react'
const buttonRef = useRef<ElementRef<'button'>>(null)
useRef as mutable container
type Props = {
children?: React.ReactNode
cssProps?: React.CSSProperties
reference?: React.MutableRefObject<HTMLDivElement>
ref2?: React.MutableRefObject<HTMLDivElement>
title?: string | React.ReactNode
logo?: React.ReactNode
}
const Timer = () => {
const [timer, setTimer] = React.useState(0)
const interValRef = React.useRef<number | null>(null)
const stopTimer = () => {
if (!interValRef.current) return
window.clearInterval(interValRef.current)
}
React.useEffect(() => {
interValRef.current = window.setInterval(() => {
setTimer(timer => timer + 1)
}, 1000)
return () => stopTimer()
}, [])
return (
<div>
HookTimer - {timer} -
<button onClick={() => stopTimer()}>Stop Timer</button>
</div>
)
}
<Timer />
Component in props
type ProfileProps = { name: string }
type PrivateProps = {
isLoggedIn: boolean
Component: React.ComponentType<ProfileProps>
}
const Profile = ({ name }: ProfileProps) => <div>Name is {name}</div>
const Private = ({ isLoggedIn, Component }: PrivateProps) => {
if (isLoggedIn) return <Component name='John' />
return <div>Login to continue</div>
}
<Private isLoggedIn={true} Component={Profile} />
Generic type Not clear what is it about, take a closer look later.
type ListProps<T> = {
items: T[]
onClick: (value: T) => void
}
const List = <T extends number>({ items, onClick }: ListProps<T>) => {
return (
<div>
<Hs>List of items</Hs>
{items.map(item => {
return (
<div onClick={() => onClick(item)} > {item} </div>
)
})}
</div>
)
}
<List items={[1, 2, 3]} onClick={item => console.log(item)} />
Never type If one type is applicable we can disallow other types with never type.
type RandomNumberType = {
value: number
}
type PositiveNumber = RandomNumberType & {
isPositive: boolean
isNegative?: never
isZero?: never
}
type NegativeNumber = RandomNumberType & {
isNegative: boolean
isPositive?: never
isZero?: never
}
type Zero = RandomNumberType & {
isZero: boolean
isPositive?: never
isNegative?: never
}
type RandomNumberProps = PositiveNumber | NegativeNumber | Zero
const RandomNumber = ({
value,
isPositive,
isNegative,
isZero
}: RandomNumberProps) => {
return (
<div>
{value} {isPositive && 'positive'} {isNegative && 'negative'}{' '}
{isZero && 'zero'}
</div>
)
}
<RandomNumber value={5} isPositive />
Template literals
/*
* Position prop can be one of
* "left-center" | "left-top" | "left-bottom" | "center" | "center-top" |
* "center-bottom" | "right-center" | "right-top" | "right-bottom"
*/
type HorizontalPosition = 'left' | 'center' | 'right'
type VerticalPosition = 'top' | 'center' | 'bottom'
type ToastProps = {
position: `${HorizontalPosition}-${VerticalPosition}`
}
const Toast = ({ position }: ToastProps) => <div>Position - {position}</div>;
<Toast position='left-top'></Toast>
type will be combination of all possible strings Html element
type ButtonProps = {
variant: 'primary' | 'secondary'
} & React.ComponentProps<'button'>
const CustomButton = ({ variant, children, ...rest }: ButtonProps) => {
return (
<button className={`class-with-${variant}`} {...rest}>
{children}
</button>
)
}
<CustomButton variant={'primary'} onClick={() => alert('clicked')}>Button text</CustomButton>
type InputPropsType = React.ComponentProps<'input'>
const Inpt = (props: InputPropsType) => <input {...props} />;
<Inpt onChange={() => alert('typed')}/>
Omit with Omit we may exclude some property from type works only with simple props, not nested objects bellow we excluded onChange type form input element.
type InpPropsType = Omit<React.ComponentProps<'input'>, 'onChange'>
const Inpt2 = (props: InpPropsType) => <input {...props} />;
<Inpt2 />
Exclude if we want to omit an object from union types Omit will not work use Exclude Take types of other components Let's take props type from the previous component.
const Inpt3 = (props: React.ComponentProps<typeof Inpt2>) => <input {...props} />;
<Inpt3 />
Polymorphic components Polymorphic component renders different html tags depends on input props. Not clear because generic type is used, take a closer look later.
type TextOwnProps<E extends React.ElementType> = {
size?: 'sm' | 'md' | 'lg'
color?: 'primary' | 'secondary'
children: React.ReactNode
as?: E
}
type TextProps<E extends React.ElementType> = TextOwnProps<E> &
Omit<React.ComponentProps<E>, keyof TextOwnProps<E>>
const Text = <E extends React.ElementType = 'div'>({
size,
color,
children,
as
}: TextProps<E>) => {
const Component = as || 'div'
return (
<Component className={`class-with-${size}-${color}`}>{children}</Component>
)
}
<>
<Text size='lg' as='h1'>Heading</Text>
<Text size='md' as='p'>Paragraph</Text>
<Text size='sm' color='secondary' as='label' htmlFor='someId'>Label</Text>
</>
Heading Paragraph Label Redux Payload in reducer
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
Thunk
export const incrementIfOdd =(amount: number): AppThunk => (dispatch, getState) => {
const currentValue = selectCount(getState())
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount))
}
}
Redux config types
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export type AppThunk<ReturnType = void> = ThunkAction< ReturnType, RootState, unknown, Action<string> >
// hooks to let types work
export const useSelectorTyped: TypedUseSelectorHook<RootState> = useSelector
export const useDispatchTyped = () => useDispatch<AppDispatch>()
Spread object - Partial<Type> works to update some prop inside an object
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "organize desk",
description: "clear clutter",
};
const todo2 = updateTodo(todo1, {
description: "throw out trash",
});
Any string with suggestions if we want to accept any string, but also want some suggests to be shown can do following trick, taken from here
type Props = {
lang: 'en' | 'fi' | 'nl' | 'sv' | Record<never, never> & string
}
as const as const makes type as narrow as possible
// without "as const"
const employee = {
name: 'Jane',
age: 30
}
type EmployeeKey = keyof typeof employee // "name" | "age"
type EmployeeValue = (typeof employee)[keyof typeof employee] // string | number
type EmployeeName = typeof employee['name'] // string
// with "as const"
const employee = {
name: 'Jane',
age: 30
} as const
type EmployeeKey = keyof typeof employee // "name" | "age"
type EmployeeValue = (typeof employee)[keyof typeof employee] // "Jane" | 30
type EmployeeName = typeof employee['name'] // "Jane"
Object keys & values
const person = {
age: 30,
name: 'Jane',
job: 'doctor',
} as const
type Keys = keyof typeof person // "age" | "name" | "job"
type Values = (typeof person)[keyof typeof person] // 30 | "Jane" | "doctor"
Array values as enums
const person = ['Jane', 'Mat', 'Olivia'] as const
type ArrayValue = (typeof person)[number] // "Jane" | "Mat" | "Olivia"
type of most nested property
type Lang = 'en' | 'fi' | 'nl' | 'sv'
type NestedObject<T = string> = {
[key: string]: T | NestedObject<T>
}
type Translations = NestedObject<Record<Lang, string>>
const translations: Translations = {
appName: {
en: 'eReceipt',
fi: 'eTosite',
nl: 'eReceipt',
sv: 'eKvitto'
},
navigation: {
home: {
en: 'Home',
fi: 'Koti',
nl: 'Home',
sv: 'Hem'
},
}
}
Prettify
type Prettify<T> = {
[K in keyof T]: T[K]
} & {}
if you hover over type intersection or nested type you may not see the shape of result type wrap your result type into Prettify and type will be unwrapped