Skip to main content

Command Palette

Search for a command to run...

TypeScript for React Developers Series: How to Get Started

Updated
5 min read
TypeScript for React Developers Series: How to Get Started
O

Fullstack developer crafting elegant web solutions with React.js, TypeScript, graphQL, Node.js and AWS

By adopting TypeScript, React developers take advantage of the language’s type safety, code analysis, and refactoring capabilities. In this article, we’ll explore the steps needed to adopt TypeScript.

Prerequisites:

  • Familiar with basic TypeScript types

  • Familiar with React.js

  • Reading the previous article

Naming Conventions:

  • use PascalCase for class, component and Enum names.

  • use camelCase for variable and function names.

  • do not use I as prefix of the interface instead use ComponentNameProps or FunctionNameParams.

  • use .generated.* as suffix for the files are generated and you don't edit them.

interface ComponentNameProps {
  position: 'left' | 'right'
}
function ComponentName({ position }: ComponentNameProps) {
  if (position === 'left') {
    return <div>left</div>
  }
  return <div>right</div>
}

interface FunctionNameParams {
  args: string[]
}
function functionName({ args }: FunctionNameParams) {
  return args.join(' ')
}

Types:

  • put the shared types in types.ts file.

  • use generics to avoid duplicating the components/functions.

  • do not export functions/types that not shared across multi-components.

  • in your component file, the type definitions must comes the first.

  • use Utility Types to avoid duplicating of the type definitions.

  • use readonly to define the data coming from your API

Components:

  • don’t use FunctionComponent<T> or FC<T> to define your component, because it's explicit about the return type, while the normal function version is implicit, read more about this codemod-replace-react-fc-typescript.

  • use ReactNode instead JSX.Element to define your children when use the normal functions, because JSX.Element is the return value of React.createElement, while ReactNode is return value of a component

  • use generic components to make your component more flexible, read more about this topic.

  • use the React.ComponentPropsWithoutRef<T> as Wrapping/Mirroring a HTML Element when you want to make component that takes all the normal props of this Element.read more about this topic

// bad
interface MyCustomLinkProps {
  href: string
  children?: React.ReactNode
}
function MyCustomLink(props: MyCustomLinkProps) {
  const { children, href } = props
  return <a href={href}>{children}</a>
}
// most recommended
function MyCustomLink(props: React.ComponentPropsWithoutRef<'a'>) {
  return <a {...props}>{children}</a>
}
  • use Fragment or use the shorthand <> instead div in wrapping the component.
// bad
function ComponentName() {
  return (
    <div>
      <section></section>
      <section></section>
    </div>
  )
}

// most recommended
function ComponentName() {
  return (
    <>
      <section></section>
      <section></section>
    </>
  )
}

Forms and Events:

  • inline event handler:
const InputComponent = () => (
  <input
    // TypeScript automatically inffers it
    onChange={(e) => {
      // logic here
    }}
  />
)
  • separated event handler:
const InputComponent = () => {
  const onChnageInputHandler = (e: React.FormEvent<HTMLInputElement>): void => {
    // your logic here
  }
  return <input onChange={onChnageInputHandler} />
}

or:

const InputComponent = () => {
  const onChnageInputHandler: React.ChangeEventHandler<HTMLInputElement> = (
    e
  ) => {
    // your logic here
  }
  return <input onChange={onChnageInputHandler} />
}
  • onSubmit definition for uncontrolled form component
const Form = () => (
  <form
    onSubmit={(e: React.SyntheticEvent) => {
      e.preventDefault()
      const target = e.target as typeof e.target & {
        email: { value: string }
        password: { value: string }
      }
      const email = target.email.value // typechecks!
      const password = target.password.value // typechecks!
      // etc...
    }}
  >
    <label>
      Email:
      <input type="email" name="email" />
    </label>
    <label>
      Password:
      <input type="password" name="password" />
    </label>
    <button type="submit">Login</button>
  </form>
)

you can use formik or any other library for handling Forms, those libraries are built on the principles of controlled components

Hooks:

  • useState hook :
const Component = () => {
  //1- inferred type:
  // `state` is inferred to be a number
  const [state, setState] = useState(0)
  // `stringState` is inferred to be a string
  const [stringState, setStringState] = useState('')
  //2- implicit type:
  const [state1, setState2] = useState<number | undefined>()
  return <></>
}
  • useCallback hook :
//   it's just like any other function
interface MemoizedFunctionParams {
params:string[]
}
const memoizedFunction = useCallback(
  (args:MemoizedFunctionParams) => {
    return 'text'
  },
  [...],
);
  • useReducer hook:
import { useReducer } from 'react'

const initialState = { count: 0 }
type ACTIONTYPE =
  | { type: 'increment'; payload: number }
  | { type: 'decrement'; payload: string }
function reducer(state: typeof initialState, action: ACTIONTYPE) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + action.payload }
    case 'decrement':
      return { count: state.count - Number(action.payload) }
    default:
      throw new Error()
  }
}
function Counter() {
  // If you don't define return type, TypeScript automatically infers it.
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement', payload: '5' })}>
        -
      </button>
      <button onClick={() => dispatch({ type: 'increment', payload: 5 })}>
        +
      </button>
    </>
  )
}
  • useRef hook :
import { useRef, useEffect } from 'react'
// This is just an example, React recommended you to use Controlled components
function Component() {
  const inputRef = useRef<HTMLInputElement>(null)
  const divRef = useRef<HTMLDivElement>(null)
  useEffect(() => {
      if (!inputRef) {
        throw Error('input not mounted !!!!')
      }
      inputRef.current.value = 'assign value'
    }, [])
    const onChnageButtonHandler = () => {
      divRef.current.innerHTML = 'we change this text here'
    }
  return (
    <div>
      <input ref={inputRef} />
      <div ref={divRef}>this is div content</div>
      <button onClick={onChnageButtonHandler}>change the content</button>
    </div>
  )
}

Context:

  • with default value:
import { createContext, useState, useContext } from 'react'

type ThemeContextType = 'light' | 'dark'
const ThemeContext = createContext<ThemeContextType>('light')
const App = () => {
  const [theme, setTheme] = useState<ThemeContextType>('light')
  return (
    <ThemeContext.Provider value={theme}>
      <MyComponent />
    </ThemeContext.Provider>
  )
}
const MyComponent = () => {
  const theme = useContext(ThemeContext)
  return <p>The current theme is {theme}.</p>
}
  • without default value:
import { createContext, useState, useContext } from 'react'

type userContextType = { name: string; email: string }
const UserContext = createContext<userContextType>()
const App = () => {
  const [user, setUser] = useState<userContextType>({
    name: 'name',
    email: 'email@mail.com',
  })
  return (
    <ThemeContext.Provider value={...{ user, setUser }}>
      <MyComponent />
    </ThemeContext.Provider>
  )
}
const MyComponent = () => {
  const { user } = useContext(UserContext)
  return <p>The current theme is {user?.name}.</p>
}

Conclusion:

In conclusion, using TypeScript with React can write safer, more maintainable code, and catch errors earlier in the development process. Additionally, TypeScript’s static typing can help improve developer productivity by providing better tooling and documentation.

TypeScript for React Developers Series

Part 2 of 3

We’ll dive into practical tips and real-world scenarios to help you effectively adopt TypeScript in your React projects. Whether you're new to TypeScript or looking to refine your skills, this guide has something for you. Let’s get started!

Up next

TypeScript for React Developers Series: Why You Should Adopt It

TypeScript is a superset of JavaScript that adds static typing, which makes your code readable, maintainable and re-usable. when you adopt TypeScript to React projects, you don’t need to add additional packages to validate props like prop-types, also...

More from this blog

C

Code & Cloud with Othmane

8 posts

Welcome to my corner of the tech universe! I'm Othmane, a passionate explorer of cloud computing and web development. This blog is my digital notebook where i share what i learned.