React with TypeScript

React is a “JavaScript library for building user interfaces”, while TypeScript is a “typed superset of JavaScript that compiles to plain JavaScript.” By using them together, we essentially build our UIs using a typed version of JavaScript. The reason you might use them together would be to get the benefits of a statically typed language (TypeScript) for your UI. This means more safety and fewer bugs shipping to the front end. Though many other frameworks and libraries adopt TypeScript by default, React remained neutral, giving the developers the option to choose between TypeScript and JavaScript

*Components

One of the core concepts of React is components. In general, there’s much to be concerned with for basic components. Let’s look at an example:

import React from 'react'
// Written as a function declaration
function Heading(): React.ReactNode{
  return <h1>My Website Heading</h1>
}
// Written as a function expression
const OtherHeading: React.FC = () => <h1>My Website Heading</h1>

Notice the key difference here. In the first example, we’re writing our function as a function declaration. We annotate the return type with React.Node because that’s what it returns. In contrast, the second example uses a function expression. Because the second instance returns a function, instead of a value or expression, we annotate the function type with React.FC for React “Function Component”. It can be confusing to remember the two. It’s mostly a matter of design choice. Whichever you choose to use in your project, use it consistently.

*Props

The next core concept we’ll cover is props. You can define your props using either an interface or a type. Let’s look at another example:

import React from 'react'
interface Props {
  name: string;
  color: string;
}
type OtherProps = {
  name: string;
  color: string;
}
// Notice here we're using the function declaration with the interface Props
function Heading({ name, color }:  Props): React.ReactNode {
  return <h1>My Website Heading</h1>
}
// Notice here we're using the function expression with the type OtherProps
const OtherHeading: React.FC <OtherProps> = ({ name, color }) => <h1>My Website Heading</h1>

-When it comes to types or interfaces, we suggest following the guidelines presented by the react-typescript-cheatsheet community:

“always use interface for public API’s definition when authoring a library or 3rd-party ambient type  definitions.”

“consider using type for your React Component Props and State, because it is more constrained.”

-Let’s look at one more example so we can see something a little bit more practical:

import React from 'react'
type Props = {
  color?: string;
  children: React.ReactNode;
  onClick: ()  => void;
}
const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
   return <button style={{ backgroundColor: color }} onClick={onClick}>{children}</button>
}

In this <Button /> component, we use a type for our props. Each prop has a short description listed above it to provide more context to other developers. The ? after the prop named color indicates that it’s optional. The children prop takes a React.ReactNode because it accepts everything that’s a valid return value of a component. To account for our optional color prop, we use a default value when destructuring it. This example should cover the basics and show you have to write types for your props and use both optional and default values.

*Hooks

-Luckily, the TypeScript type inference works well when using hooks. This means you don’t have much to worry about. For instance, take this example:

// `value` is inferred as a string
// `setValue` is inferred as (newValue: string) => void
const [value, setValue] = useState(‘’)

-TypeScript infers the values given to use by the useState hook. This is an area where React and TypeScript work together. On the rare occasions where you need to initialize a hook with a null value, you can make use of a generic and pass a union to correctly type your hook. See this instance:

type User = {
  email: string;
  id: string;
}
// the generic is the < >
// the union is the User | null
// together, TypeScript knows, "Ah, user can be User or null".
const [user, setUser] = useState<User | null>(null);

– The other place where TypeScript shines with Hooks is with userReducer, where you can take advantage of discriminated unions. Here’s a useful example:

type AppState = {};
type Action =  | { type: "SET_ONE"; payload: string | { type: "SET_TWO"; payload: number };
export function reducer(state: AppState, action: Action): AppState {
  switch (action.type) {
    case "SET_ONE":
      return {
        ...state,
        one: action.payload 	// `payload` is string
      };
    case "SET_TWO":
      return {
        ...state,
        two: action.payload 	// `payload` is number
      };
    default:
      return state;
  }
}

As you can see, Hooks don’t add much complexity to the nature of a React and TypeScript project. If anything, they lend themselves well to the duo.

*Common Use Cases

This section is to cover the most common use cases where people stumble when using TypeScript with React.

-Handling Form Events 

One of the most common cases is correctly typing the onChange used on an input field in a form. Here’s an example:

import React from 'react'
    const MyInput = () => {
    const [value, setValue] = React.useState(' ')
    // The event type is a "ChangeEvent"
 	 // We pass in "HTMLInputElement" to the input
  function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    setValue(e.target.value)
  }
  return <input value={value} onChange={onChange} id="input-example"/>}

-Extending Component Props

Sometimes you want to take component props declared for one component and extend them to use them on another component. But you might want to modify one or two. Well, remember how we looked at the two ways to type component props, types or interfaces? Depending on which you used determines how you extend the component props. Let’s first look at the way using type:

import React from 'react'
;type ButtonProps = {			
type ContainerProps = ButtonProps & {
    color: string;				    
height: number;
    text: string;				}
}
const Container: React.FC<ContainerProps> = ({ color, height, width, text }) => {
      return <div style={{ backgroundColor: color, height: `${height}px` }}>{text}</div>
}

If you declared your props using an interface, then we can use the keyword extends to essentially “extend” that interface but make a modification or two:

import React from 'react';
interface ButtonProps 
{		interface ContainerProps extends ButtonProps {
    color: string;			    height: number;
    text: string;			}
}
const Container: React.FC<ContainerProps> = ({ color, height, width, text }) => {
  return <div style={{ backgroundColor: color, height: `${height}px` }}>{text}</div>
}

Both methods solve the problem. It’s up to you to decide which to use. Personally, extending an interface feels more readable, but ultimately, it’s up to you and your team.

*Reasons and benefits to Use TypeScript with React

Easy to read and understand components  (With TypeScript, it’s easy to define Prop types, making the code much easier to read and use. And this will accompany by IntelliSense support plus static type checking.)

-Better support for JSX  (Another additional benefit of TypeScript + React is that it provides better IntelliSense, code completion for JSX.)

-Default TypeScript support for common libraries (Most of the commonly used libraries are supported with Type Definitions)

-Gradual adoption for existing projects(Going for TypeScript is not an on-off switch. It is instead a gradual adaptation depending on the current condition of the project. Suppose you have decided to use TypeScript for an ongoing React project. In that case, you need to look at how you can use JavaScript and TypeScript side by side and gradually increase the TypeScript coverage.)

-Benefits

*Static Type checking (Static Type checking helps to identify errors earlier)

*Better Refactoring (With TypeScript, refactoring is much easier since we know  the exact Types and where to change them. And early detection of that something went wrong will ensure that you are not breaking anything.)

*Less amount of undefined errors (The risk of appearing Undefined errors at runtime is less since the TypeScript compiler also detects them at transpile time.)

*Better readability and maintainability (With Type Definitions, the codebase will be much readable and maintainable. You can easily follow object-oriented programming principles and use the constructs like Interfaces to structure your code with the least coupling.)