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