React TypeScript

References

Environment Setup

Summary

  • event
    • React.MouseEvent
    • React.ChangeEvent
  • styles
    • React.CSSProperties
  • HTML element
    • HTMLButtonElement
    • HTMLInputElement
  • children
    • React.ReactNode
  • pass component as prop
    • React.ComponentType<SourceComponentType>
  • Class component
  • Generic
    • type CustomPropsType<T> = {}
    • const Component = <T extends {}>(props: CustomPropsType<T>) => {}
  • Restricting props
  • Template literals & Exclude
    • Exclude<UnionType, ExcludedMembers>
  • Wrapping HTML & Omit
    • React.ComponentProps<htmlString | typeof CustomPropsType>
    • Omit<Type, Keys>
  • Extracting component prop type
  • Polymorphic component

Props

  • basic
  • advanced
    • status condional display - source code
    • children props with React.ReactNode - source code
      • @type/react version 16: import React at the top
      • @type/react version 17: just use it for typescript
    • question mark '?' for optional parameter - source code
    • NOTE: About '?'
      • To mention that a particular variable is optional.
      • To pre-check if a member variable is present for an object.
  • event
    • <button>
      • click event, React.MouseEvent - source code
      • NOTE: event more specific for html button - React.MouseEvent<HTMLButtonElement>
    • <input>
  • style
    • React.CSSProperties
  • More

Hooks

  • Conclution
  • useState
    • future value - source code
      const [user, setUser] = useState<UserType | null>(initialValue)
      const getValue = () => {
          const name = user?.name
      }
    • type assertion - source code
      • access user.name without a check
      • If we're confident user will be initialized soon after setup, and always have value after, we can use type assertion
      const [user, setUser] = useState<UserType>({} as UserType)
      const getValue = () => {
          const name = user.name
      }
  • useReducer
    • source code
    • more specific: union type for action.type
    • handle having payload or not: ActionType = UpdateType | ResetType
  • useContext
  • useRef
    • html element
    • mutable ref
      • handle uesRef() type
        • e.g. const intervalRef = useRef<number | null>(null)
      • check ref exist before clearInterval
        • e.g. if (intervalRef.current) window.clearInterval(intervalRef.current)
      • source code - mutable ref

Class Component

  • handle type of the component's props & state
    class ClassComponent extends React.Component<ClassComponentProps, ClassComponentState> {}
  • source code

Component as props

Generics

  • mention generic type T(NOTE: use any lable is OK) after type variable
    • type ListProps<T> = {}
    • it means ListProps accepts a variable call T
  • assign T
    • type ListProps = { item: string[] | number[] } -> type ListProps<T> = { item: T[] }
  • use the type
    • const ListComponent = (props: ListProps<T>) => {}
  • specify what T can be before the parentheses
    • T needs to extend a base type
    • const ListComponent = <T extends {}>(props: ListProps<T>) => {}
  • then we can pass in an array of any type at ListComponent, and will not throw an error
  • source code - generic props

Restricting props

  • generate basic value type

    • type ValueType = { value: number; }
  • extend type for isPositive etc.

    type isPositiveType = ValueType & {
        isPositive: boolean;
        isNegative?: never;
        isZero?: never;
    }
  • use in component props type

    • type ComponentPropsType = isPositiveType | isNegativeType | isZeroType
  • source code - restricting props

  • usage

    • <RestrictingPropsComponent value={10} isPositive />

Template literals & Exclude

  • build type for horizontal & vertical
    • type HorizontalPosition = 'left' | 'center' | 'right';
    • type VerticalPosition = 'top' | 'center' | 'bottom';
  • exclude center-center
    • Exclude<${HorizontalPosition}-${VerticalPosition}, 'center-center`>
  • add 'center`
    • type Postion = ExclusivePostion | 'center'
  • source code - template literals and exclude

Wrapping HTML & Omit

  • wrapping
    • type CustomButtonPropsType = { specificTypeEgTypeForClass: string } & React.ComponentProps<'button'>

    • usage

      const CustomButton = ({ classname, children, ...restProps }: CustomButtonPropsType) => {
          return <button className={classname} {...restProps}>{ children }</button>
      }
    • source code - custom input

  • Omit - omit takes an object type and removes the specific properties
    • scenario - restrict children type to just string

    • define children for string

    • omit the type we want to omit in React.ComponentProps

      type ComponentPropsType = {
          className: 'primary' | 'secondary';
          children: string;
      } & Omit<React.ComponentProps<'button'>, 'children'>
    • source code - omit button children

Extracting component prop type

  • React.ComponentProps<typeof Component>

    import SourceComponent from 'path/to/source/component'
    const AnotherComponent = (props: React.ComponentProps<typeof SourceComponent>) => {}
  • source code

Polymorphic component

  • scenario - passing a property controls what HTML element is rendered in the children component

    • goal
      import Text from 'path/to/text/component'
      
      const App = () => {
          return (
              <Text as="h1">context</Text>
          )
      }
      
      // should be: <h1>context</h1>
  • implement steps

    1. initialize

      type TextPropsType = {
          size?: 'sm' | 'md' | 'lg';
          color?: 'primary' | 'secondary';
          children: React.ReactNode;
          as?: string
      }
      
      function Text({ size, color, children, as }: TextPropsType) {
          const Component = as || 'div'
          return (
              <Component className={`class-width-${size}-${color}`}>{ children }</Component>
          );
      }
      // Error: Type '{ children: ReactNode; className: string; }' is not assignable to type 'IntrinsicAttributes'. Property 'children' does not exist on type 'IntrinsicAttributes'.
    2. as props can't be any random string -> React.ElementType

    3. improve - label should have htmlFor attribute

      type TextOwnPropsType<E extends React.ElementType> = {
          size?: 'sm' | 'md' | 'lg';
          color?: 'primary' | 'secondary';
          children: React.ReactNode;
          as?: E
      }
      
      type TextPropsType<E extends React.ElementType> = TextOwnPropsType<E> & React.ComponentProps<E>
    4. children might collide with children with div tag -> omit the keys that are specified as part of the TextOwnPropsType

      type TextPropsType<E extends React.ElementType> = TextOwnPropsType<E> & Omit<React.ComponentProps<E>, keyof TextOwnPropsType<E>>
    5. add generic type for component

      const Text = <E extends React.ElementType = 'div'>({ size, color, children, as }: TextPropsType<E>) => {
          const Component = as || 'div'
          return (
              <Component className={`class-width-${size}-${color}`}>{ children }</Component>
          );
      }
    6. summary

      type TextOwnPropsType<E extends React.ElementType> = {
          size?: 'sm' | 'md' | 'lg';
          color?: 'primary' | 'secondary';
          children: React.ReactNode;
          as?: E;
      }
      
      type TextPropsType<E extends React.ElementType> = TextOwnPropsType<E> & Omit<React.ComponentProps<E>, keyof TextOwnPropsType<E>>
      
      const Text = <E extends React.ElementType = 'div'>({ size, color, children, as }: TextPropsType<E>) => {
          const Component = as || 'div'
          return (
              <Component className={`class-width-${size}-${color}`}>{ children }</Component>
          );
      }
  • source code - polymorphic component