ReactJs with Typescript

(Useful : 1] CheatSheet 2] Click)


React is one of the most popular front-end JavaScript libraries for building user interfaces. Using TypeScript for your React projects can lead to a more robust codebase with less errors. 


// main.js

function Increment(num){
    return num + 1
}

Increment(100)
Increment("one=hundred")  // Works ! No compile-time error

//------------------------------------------------------------------------------------

// main.ts

function Increment(num:number){
    return num + 1
}

Increment(100)
Increment("one=hundred")  // Compile-time error

Some advantages of using Typescript with ReactJs are as followed :

  • Better Autocompletion - It prevents us from remembering props and function parameters, which allows you to focus more on building the application.You won't need to check what the state looks like or what props your component accepts; your editor will automatically suggest the right properties while you're typing.
  • Compile-Time Errors - By using Typescript you'll receiev errors straight into the terminal during compilation which means lesser runtime errors.
  • Code ReadabilityWith TypeScript, it’s easy to define Prop types, making the code much easier to read and use. This way we can easily document the code for other to work on.
  •  Easy Migration - Typescript is basically "JS + Extra Features" so we can easily write Vanilla JS inside Typescript files.

Some disadvantages of using Typescript with ReactJs are as followed :

  • Missing Type Definations - Not all modules ship with Typescript support. If you come acrosss such library, you'll need to create your own type definations for it.
  • Compilation Time - When using Typescript we also add an extra step of "transpilation" in the process which increases build-time for the application.
  • Extra Configuration - With Typescript comes the "tsconfig" file, which is an configuration file that needs to be managed throught the project.
NOTE : When using Typescript, if you dont explicitly mention the type or forgot to mention the type of an object, then Typescript compiler will automatically Infer it during the transpilation process.

---------------------------------------------------------------------------------------------------------------

React Typescript Setup


1] Start Fresh Project

To generate the boilerplate code required to get started with React Typescript, execute the below command. It'll generate all the necessary files required to get started with using Typescript for React components.

By using the "--template typescript", the Create-React-App builds the app with Typescript configuration. You get the same base as the JavaScript projects, but TypeScript support has been built into the configuration.

NOTE : By default you dont need to run extra command like "tsc" to transpile your files to Javascript, CRA does that for you when you run "start".


// Install Typescript
npm install -g typescript

// Generate boilerplate code
npx create-react-app  <app-name> --template typescript

NOTE : The ".ts" files are pure typescript files, while ".tsx" extension files are a replacement for ".js" files containing React components. TSX is similar to JSX except it's TypeScript with the JSX language extension.


2] Add Typescript to existing project

If you want to add TypeScript to the existing app, then install TypeScript and other required types. 


 npm install --save typescript @types/node @types/react @types/react-dom @types/jest

After that rename the files to .ts or .tsx and then start the server. This will generate the tsconfig.json file automatically and you are ready to write React in TypeScript.

NOTE : In TypeScript, ".ts" files are used for regular TypeScript code that does not include JSX syntax, while ".tsx" files are used specifically for TypeScript code that includes JSX syntax for defining React components. The TypeScript compiler treats these files differently, with ".tsx" files including built-in support for JSX syntax, while ".ts" files do not.

---------------------------------------------------------------------------------------------------------------

React Typescript Types

When using React with TypeScript, we can type-safe various things like React components, Props, Event Objects, Hooks, etc. The type declarations for React and many other libraries that are used with TypeScript come from DefinitelyTyped, a community-driven project that provides type declarations for popular JavaScript libraries and tools.To use these react type declarations in a TypeScript project, you can install the @types/react package using a package manager like npm or Yarn.

Below are some common react types and their descriptions of when to use each :

  • React.FC : This type should be used for functional components in React. It provides type safety and enables TypeScript to check that the component is returning the right type of data.
  • React.Component : This type should be used for class-based components in React. It provides the same benefits as React.FC, but for class-based components.
  • React.PureComponent : This type should be used for class-based components that need to be optimized for performance. It performs a shallow comparison of the component's props and state to determine if the component should be re-rendered.
  • React.ReactNode : This type should be used to represent the return type of a component. It's a union type of all valid JSX types and primitive types.
  • React.ReactElement : This type should be used to represent a single JSX element.
  • React.CSSProperties : This type should be used to define styles for a component.
  • React.FormEvent, React.MouseEvent, React.KeyboardEvent, and React.ChangeEvent : These types should be used to define the type of event object that a handler function will receive.
  • React.RefObject : This type should be used to create a ref that can be attached to a component.
  • React.Context : This type should be used to create a context object that can be passed down to child components.
  • React.Provider and React.Consumer : These types should be used to create a provider and consumer component, respectively, for a context object.
  • React.Fragment : This type should be used to wrap multiple JSX elements that don't need a container element.
  • React.Suspense and React.Lazy : These types should be used for lazy loading components.
  • React.ErrorInfo : This type should be used to get error information for componentDidCatch().
  • React.ReactNodeArray and React.ReactText : These types should be used to represent arrays of JSX elements and primitive types in JSX, respectively.

---------------------------------------------------------------------------------------------------------------

Typing Component Props

In regular react we could create and pass props to the components freely and dynamically, but when using typescript we have to add types to our props before we use them inside our components. This way we get type-safety and also better autocompletion for props inside the components when writing code. 

There are many ways to add types to component props, all do the same thing i.e add types to props, but differ in their syntax, some common ways are as follows :


1] Types with Interface

Define an Interface with all the default and optional component props and set their types, then just simply add it as argument type inside the component.


// App.tsx

import './App.css';

interface IProps {
  name: string | number,
  age: number,
  // optional prop
  married?: boolean,
  friends?: string[]
}

const Person = (props: IProps) => {
  return (
    <>
      <h2> Name : {props.name}</h2>
      <h2> Age : {props.age}</h2>
      <h2> Married : {props.married}</h2>
      <h2> Friends : {props.friends}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
        <Person name="deepesh" age={19} married={false} />
        <Person name={200} age={29} friends={["Rohan", "Kiran"]} />
      </div>
    </>
  );
}

export default App;

NOTE : To make a prop optional in the props interface, mark it with symbol ?.

As we know Interfaces can be extended, we can use this to breakdown our props into different Interfaces which extend each other's properties.


// App.tsx

import './App.css';

interface educationProps {
  school?:string,
  score:number
}

interface personProps extends educationProps{
  name: string | number,
  age: number,
  // optional prop
  married?: boolean,
  friends?: string[]
}

const Person = (props: personProps) => {
  return (
    <>
      <h2> Name : {props.name}</h2>
      <h2> Age : {props.age}</h2>
      <h2> Married : {props.married}</h2>
      <h2> Friends : {props.friends}</h2>
      <h2> Friends : {props.school}</h2>
      <h2> Friends : {props.score}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
    <Person name="deepesh" age={19} married={false} school="St.Xaviers" score={33}/>
    <Person name={200} age={29} friends={["Rohan", "Kiran"]} score={99}/>
      </div>
    </>
  );
}

export default App;


2] Types with Type-Alias

We can also use type-aliases instead of OOP interfaces to define component props. It is prefered to use Type alias for typing component props as it's more constrained then Interfaces which can be extended.


// App.tsx

import './App.css';

type personProps = {
  name: string | number,
  age: number,
  // optional prop
  married?: boolean,
  friends?: string[]
}

const Person = (props: personProps) => {
  return (
    <>
      <h2> Name : {props.name}</h2>
      <h2> Age : {props.age}</h2>
      <h2> Married : {props.married}</h2>
      <h2> Friends : {props.friends}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
        <Person name="Aryan" age={19} married={false} />
        <Person name={200} age={29} friends={["Rohan", "Kiran"]} />
      </div>
    </>
  );
}

export default App;

We can also pass the Props type along with the React.FC type using the brackets "<>" notation, as shown in the below example code.


// App.tsx

import './App.css';

type personProps = {
  name: string | number,
  age: number,
  // optional prop
  married?: boolean,
  friends?: string[]
}

// THIS WORKS TOO !
// const Person:React.FC<personProps> = ({name,age,married,friends}) => {  }

const Person:React.FC<personProps> = (props) => {
  return (
    <>
      <h2> Name : {props.name}</h2>
      <h2> Age : {props.age}</h2>
      <h2> Married : {props.married}</h2>
      <h2> Friends : {props.friends}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
        <Person name="Aryan" age={19} married={false} />
        <Person name={200} age={29} friends={["Rohan", "Kiran"]} />
      </div>
    </>
  );
}

export default App;

Similar to how Interfaces can extend each other, we can use the "Type Intersections" to combine the properties of multiple type aliases.


// App.tsx

import './App.css';

type educationProps = {
  school?:string,
  score:number
}

type personProps = {
  name: string | number,
  age: number,
  // optional prop
  married?: boolean,
  friends?: string[]
}

type Props = educationProps & personProps;

const Person = (props: Props) => {
  return (
    <>
      <h2> Name : {props.name}</h2>
      <h2> Age : {props.age}</h2>
      <h2> Married : {props.married}</h2>
      <h2> Friends : {props.friends}</h2>
      <h2> Friends : {props.school}</h2>
      <h2> Friends : {props.score}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
    <Person name="deepesh" age={19} married={false} school="St.Xaviers" score={33}/>
    <Person name={200} age={29} friends={["Rohan", "Kiran"]} score={99}/>
      </div>
    </>
  );
}

export default App;


3] Destructuring of Props

We can also destructure the props rather than just accessing them using the props object passed to the functional component.


// App.tsx

import './App.css';

interface personProps {
  name: string | number,
  age: number,
  married?: boolean,
  friends?: string[]
}

const Person = ({name,age,married,friends}: personProps) => {
  return (
    <>
      <h2> Name : {name}</h2>
      <h2> Age : {age}</h2>
      <h2> Married : {married}</h2>
      <h2> Friends : {friends}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
        <Person name="deepesh" age={19} married={false} />
        <Person name={200} age={29} friends={["Rohan", "Kiran"]} />
      </div>
    </>
  );
}

export default App;

or we can skip writing the Interface or Type Alias and define the prop types directly into the Functional component.


// App.tsx

import './App.css';

const Person = ({name,age,married}: {name:string ,age:number ,married?:boolean})=> {
  return (
    <>
      <h2> Name : {name}</h2>
      <h2> Age : {age}</h2>
      <h2> Married : {married}</h2>
    </>
  )
}

function App() {

  return (
    <>
      <div>
        <Person name="deepesh" age={19} married={false} />
        <Person name="Rohan" age={29}/>
      </div>
    </>
  );
}

export default App;


4] Default Prop Values

Before React 18,when defining the Types for component props we can make some props optional by mentioning the "?" symbol, but we can't define a default value to props like that. To add default values you can set default props on a functional component by adding a static property named defaultProps to the component function itself.

If the values for props are not provided then the default values will be used to render the components, below is an example.


// App.tsx (React 17)

import './App.css';

type personProps = {
  name: string | number,
  age: number,
  gender:string,
}

const Person = (props: personProps) => {
  return (
    <>
      <h2> Name : {props.name}</h2>
      <h2> Age : {props.age}</h2>
      <h2> Gender : {props.gender}</h2>
    </>
  )
}

// Sets default values to props
const PersonDefaultProps =  { name:"Unknown",age:20, gender:"Unknown" }
Person.defaultProps = PersonDefaultProps;

function App() {
  return (
    <>
      <Person name="deepesh" age={19} gender="Male" />
      <Person/>
    </>
  );
}

export default App;

In React 18 we can set the default values for the props just like in the regular reactjs without using the "defaultProps" property.


// App.tsx (React 18)

import './App.css';

type personProps = {
  name?: string | number,
  age?: number,
  gender?:string,
}

// Setting props with default values
const Person = ({ name="Unknown",age=200, gender="Unknown" }:personProps) => {
  return (
    <>
      <h2> Name : {name}</h2>
      <h2> Age : {age}</h2>
      <h2> Gender : {gender}</h2>
    </>
  )
}


function App() {
  return (
    <>
      <Person name="deepesh" age={19} gender="Male" />
      <Person name="napoleon"/>
    </>
  );
}

export default App;


5] Children Prop  (Useful :1] Click)

Children is a special prop that allows us to pass in any type of element. It could be a number, a string, a boolean, an array of elements or even another component. All the values or components defined inside the opening and closing tag of the react component will be rendered at "props.children" inside that component. When using Vanilla React we could pass in any value without defining the types beforehand.

When using Typescript with React we need to explicitly define the type for the children prop, in such cases the Union type (|) comes handy to define multiple types for a single prop. Alternatively, you can set the type of children prop to "any" so it'll behave same as it used to in regular React application without Typescript.

Example] In the below example we define the type for the special children prop.


// App.tsx

import "./App.css"

type personProps ={
  name:string,
  age:number,
  // Type the children prop
  children:string | JSX.Element | React.ReactNode
}

const Person = (props:personProps)=>{
  return(
    <>
    <h2> I am {props.name} and {props.age} years old !</h2>
    {props.children}
    </>
  )
}

export default function App() {
  return (
    <>
    <Person name="Deepesh" age={19} children="I am a String !"/>
    <Person name="Rohan" age={18}>
      <h1> I am a Big String !</h1>
      <button> Click me </button>
    </Person>
    </>
  )
}

Some common types which can be used as type for children prop :

  • ReactElement
  • JSX.Element
  • React.ReactNode
NOTEIf we explicitly type the children prop, the ReactNode is generally the best type choice for the props.children since it's wider than other types.

---------------------------------------------------------------------------------------------------------------

React Types

In the above example we saw how we can add types to the component props that we are passing, in this part we'll see some useful types we can use for react components which accept other react components as their props. You can use them as types for regular props or as a return value too, but mostly you'll be using these types with the special "children" prop.


1] ReactElement

It is the most basic of the types. It represents a single child element. When typed to a prop, that prop can only accept a single react element as value. You can think of ReactElement as a primitive type of component. Other types listed below are built on top of this basic interface.


// App.tsx

import { ReactElement } from "react"
import "./App.css"

type Props = {
  name: string,
  age: number,
  // Takes a single react element
  extraStuff?: ReactElement,
  children?: ReactElement
}

// Object with Type and Props
const Person = (props: Props):ReactElement => {
  return (
    <>
      <h2> I am {props.name} , I am {props.age} years old !</h2>
      {props.extraStuff}
      {props.children}
    </>
  )
}


export default function App() {
  return (
    <>
      <div>
        <Person name="Deepesh" age={19} extraStuff={<h1> Hello World ! </h1>} />
        <Person name="Deepesh" age={19}>
          <h1> Hello World ! </h1>
        </Person>
      </div>
    </>
  )
}


NOTE : A ReactElement Type represents an Object that has a specific Type and takes Props. In above example, Person is an ReactElement Type object.

In some cases you may want to accept multiple ReactElement objects, we can do that by mentioning the Type as an Array of ReactElements.


// App.tsx

import { ReactElement } from "react"
import "./App.css"

type Props = {
  name: string,
  age: number,
  // Takes multiple react elements
  children?: ReactElement[]
}

const Person = (props: Props) => {
  return (
    <>
      <h2> I am {props.name} , I am {props.age} years old !</h2>
      {props.children}
    </>
  )
}


export default function App() {
  return (
    <>
      <div>
        <Person name="Deepesh" age={19}>
          <h1> Hello World ! </h1>
          <h2> Hello World ! </h2>
          <h3> Hello World ! </h3>
        </Person>
      </div>
    </>
  )
}


2] JSX.Element

The JSX.Element is an ReactElement object whose Type and Props are of type "any", so they are more or less the sameJSX.Element is a ReactElement, with the generic type for props and type being any. It exists, as various libraries can implement JSX in their own way.

NOTE : If you dont specify any Type for your react component, then it'll be considered as JSX.Element and not ReactElement.


// App.tsx

import "./App.css"

type Props = {
  name: string,
  age: number,
  // Takes a single JSX element
  extraStuff?: JSX.Element,
  children?: JSX.Element
}

// Person is of type JSX.Element
const Person = (props: Props) : JSX.Element => {
  return (
    <>
      <h2> I am {props.name} , I am {props.age} years old !</h2>
      {props.extraStuff}
      {props.children}
    </>
  )
}

export default function App() {
  return (
    <>
      <div>
        <Person name="Deepesh" age={19} extraStuff={<h1> Hello World ! </h1>} />
        <Person name="Deepesh" age={19}>
          <h1> Hello World ! </h1>
        </Person>
      </div>
    </>
  )
}

In some cases you may want to accept multiple JSX.Element objects, we can do that by mentioning the Type as an Array of JSX.Element objects.


// App.tsx

import "./App.css"

type Props = {
  name: string,
  age: number,
  // Takes multiple JSX elements
  children?: JSX.Element[]
}

const Person = (props: Props) : JSX.Element => {
  return (
    <>
      <h2> I am {props.name} , I am {props.age} years old !</h2>
      {props.children}
    </>
  )
}


export default function App() {
  return (
    <>
      <div>
        <Person name="Deepesh" age={19}>
          <h1> Hello World ! </h1>
          <h2> Hello World ! </h2>
          <h3> Hello World ! </h3>
        </Person>
      </div>
    </>
  )
}


3] React.ReactNode

ReactNode is wider, it can be text, number, boolean, null, undefined, a portal, a ReactElement, or an array of ReactNodes. It represents anything that React can render. React node itself is a representation of the virtual DOM. So ReactNode is the set of all possible return values which can be returned by a component.


// App.tsx

import "./App.css"

type Props = {
  name: string,
  age: number,
  extraStuff?: React.ReactNode,
  children?: React.ReactNode
}

const Person = (props: Props) => {
  return (
    <>
      <h2> I am {props.name} , I am {props.age} years old !</h2>
      {props.extraStuff}
      {props.children}
    </>
  )
}


export default function App() {
  return (
    <>
      <div>
        <Person name="Deepesh" age={19}
extraStuff={<> <h1> Hello World ! </h1> <p> How are you ? </p></>} />
        <Person name="Deepesh" age={19}>
          <h1> Hello World ! </h1>
          This is a string
          <p> How are you ? </p>
        </Person>
      </div>
    </>
  )
}



4] React.CSSProperties

Sometimes you may want to pass CSS properties as the prop value to the component, you can even type-safe the prop to accept only CSS properties as value by using the "React.CSSProperties" type.


// App.tsx

import "./App.css"

type Props = {
  name: string,
  age: number,
  headStyle?: React.CSSProperties
}

const Person = (props: Props) => {
  return (
    <>
   <h2 style={props.headStyle}> I am {props.name} , I am {props.age} years old !</h2>
    </>
  )
}


export default function App() {
  return (
    <>
  <Person name="Deepesh" age={19}
headStyle={{ backgroundColor: "red", color: "blue" }} />
      <Person name="Rohan" age={17}
headStyle={{ backgroundColor: "blue", color: "red" }} />
    </>
  )
}


---------------------------------------------------------------------------------------------------------------

Functional Components (Useful : 1] Click 2] Click)

In the above part we saw some special types for react functional components and their props. In this part we'll see "React.FC" or "React.FunctionComponent", which is another type we can use to type-safe our react components. This is just an alternative way to define functional components when using TS with React.

As compared to other types like ReactNode or JSX.Element, the React.FC is generally discouraged due to the following reasons :

  • Implicit definition of children prop.
  • Cannot use generics with it.
  • The syntax is longer and confusing.

Example] When using React.FC, we define our props types inside the "<>" brackets, rest all functionality remains the same.


// Car.tsx

import React from "react"

type carProps = {
    brand: string,
    year: number,
    forSale: boolean
}

const Car: React.FC<carProps> = (props) => {
    const sale: String = props.forSale ? "for Sale" : "Not for Sale";
    return (
        <>
          <h1> I am a Car of {props.brand} from {props.year} and I am {sale} </h1>
        </>
    )
}

export default Car;

//--------------------------------------------------------------------------------

// App.tsx

import "./App.css"
import Car from "./Car"

export default function App() {
  return (
    <>
      <div>
        <Car brand="BMW" year={2013} forSale={false} />
        <Car brand="MERCEDES" year={2003} forSale={true} />
      </div>
    </>
  )
}

We can also directly destructure the props and define their types inline rather than using any interface or type defination.


// Car.tsx

import React from "react"

const Car: React.FC<{ brand: string, year: number, forSale: boolean}> =
    ({ brand, year, forSale }) => {
    const sale: String = forSale ? "for Sale" : "Not for Sale";
    return (
        <>
        <h1> I am a Car of {brand} from {year} and I am {sale} </h1>
        </>
    )
}

export default Car;

//-------------------------------------------------------------------------------

// App.tsx

import "./App.css"
import Car from "./Car"

export default function App() {
  return (
    <>
      <div>
        <Car brand="BMW" year={2013} forSale={false} />
        <Car brand="MERCEDES" year={2003} forSale={true} />
      </div>
    </>
  )
}


Implicit children definition (Before React 18)

  • Before the React 18 type updates, React.FunctionComponent provided an implicit definition of children , which was heavily debated and is one of the reasons why React.FC was removed from the Create-React-App TypeScript template.


The Implicit definition of children is a bit confusing and causes to implicitly take children of type ReactNode. Even if your component doesn’t need children, using React.FC opens your component’s props to it and allows us to use the children prop without defining the type.

Example] In the below example, we have not defined the children prop explictly inside carProps, yet we can use it.


// USING REACT VERSION 17

// Car.tsx

import React from "react"

type carProps = {
    brand: string,
    year: number,
    forSale: boolean
}

const Car: React.FC<carProps> = ({ brand, year, forSale, children}) => (
    <>
        <h1> I am a Car of {brand} from {year} and I am {forSale} </h1>
        {children}
    </>
)

export default Car;

//-----------------------------------------------------------------------------

// App.tsx

import "./App.css"
import Car from "./Car"

export default function App() {
  return (
    <>
      <div>
        <Car brand="BMW" year={2013} forSale={false}>
        <h3> Hi I am Deepesh !</h3>
        </Car>
        <Car brand="MERCEDES" year={2003} forSale={true} />
      </div>
    </>
  )
}

NOTE : The Implicit children definition feature is removed from React.FC in React version 18, now you need to explicitly define its type before using it.


// USING REACT VERSION 18

// Car.tsx

import React from "react"

type carProps = {
    brand: string,
    year: number,
    forSale: boolean,
    children?: React.ReactNode
}

const Car: React.FC<carProps> = ({ brand, year, forSale, children}) => (
    <>
    <h1> I am a Car of {brand} from {year} and I am {forSale} </h1>
    {children}
    </>
)

export default Car;

//--------------------------------------------------------------------

// App.tsx

import "./App.css"
import Car from "./Car"

export default function App() {
  return (
    <>
      <div>
        <Car brand="BMW" year={2013} forSale={false}>
          <h2> Hello World !</h2>
          <h2> How you doing ? </h2>
          </Car>
        <Car brand="MERCEDES" year={2003} forSale={true} />
      </div>
    </>
  )
}


Generic Components

Generic props are useful when you want to create a reusable data-driven component where the shape of the data varies. Reusable form, list, and table components are examples where generic props are handy. To create generic components you can use either an Interface or Type object.

Example] Creating a generic React component with TypeScript that can display a list of names and IDs, which can be of different types.


// App.tsx

import "./App.css"


type GProps<A, B> = {
  names: A[],
  ids: B[]
}


const CustomerList = <A,B>(props: GProps<A, B>) => {

  const nameList: A[] = props.names;
  const IdList: B[] = props.ids;

  return (
    <div>
      <h2> Names : </h2>
      {nameList.map((item) => <> {item} </>)}
      <h2> Ids : </h2>
      {IdList.map((item) => <> {item} </>)}
    </div>
  )
}


export default function App() {
  return (
    <>
      <div>
        <h1> Hello World !</h1>
        <CustomerList names={["Deepesh", "Rohan", "Kiran"]} ids={[11, 22, 33]} />
        <CustomerList names={[11, 22, 33]} ids={["Deepesh", "Rohan", "Kiran"]} />
      </div>
    </>
  )
}

Example] Creating a generic React component with TypeScript that can display a list of strings or numbers.


// App.tsx

import "./App.css";

type listProps<T> = {
  inputList: T[];
};

function DisplayList<T extends string | number>(props: listProps<T>) {

  const tailwindStyles = "text-2xl font-bold p-3 my-3 bg-red-400 "
                          +"hover:bg-red-500 text-white text-center"
  return (
    <div>
      { props.inputList.map((value) => (
          <h1 className={tailwindStyles}> {value} </h1>
        )) }
    </div>
  );
}

export default function App() {
  return (
    <>
      <div>
        <DisplayList inputList={[1, 2, 3, 4, 5]} />
        <DisplayList inputList={["Cake", "Burger", "Pizza"]} />
      </div>
    </>
  );
}

NOTE : When creating generic component with single type parameter Eg - (<T>), sometimes the typescript compiler may fail to recognize it as generic and infer it as jsx element due to similar brackets. To overcome this we can extend the generic type to other types or add a comma to it at the end. Eg - (<T extends string> or <T,>)

---------------------------------------------------------------------------------------------------------------

React Hooks

In this part we'll se how to type-safe react hooks with Typescript. Their functionalities remain the same with just a simple change in their syntax.


1] useState() Hook

The useState value can be inferred from the initial value you set when you call the function, this way you can use it with any syntax change.


  const App = () => {
   
    const [title, setTitle] = useState("Hello") // type is string

    const changeTitle = () => {
      setTitle(9) // error: number not assignable to string!
    }
  }

But when you need to initialize your state with values like null or undefined , then you need to add a Union type when you initialize the state.


 // mytitle is string
 const [mytitle, setmyTitle] = useState<string>()

 // title is string or null
 const [title, setTitle] = useState<string | null>(null)

 // score is number or undefined
 const [score, setScore] = useState<number | undefined>(undefined)

 // scores is array of numbers
 const [scores, setScores] = useState<number[]>()

When you have a complex object as the state value, you can create an interface or a type for that object as follows.


  interface Member {
    username: string,
    age?: number
  }
 
  const [member, setMember] = useState<Member | null>(null)


2] useEffect() & useLayoutEffect() Hooks

The useEffect()anduseLayoutEffect()both accept a callback function as first parameter we can apply type to that callback function but generally we don’t need to type the because they don’t deal with returning values. The cleanup function for theuseEffect()hook is not considered a value that can be changed either. You can write these hooks as normal.


import { useEffect } from 'react';

interface Props {
  userId: number;
}

function UserProfile({ userId }: Props) {

  useEffect(():void => {
    // This is the callback function for the useEffect hook
    console.log(`User ID: ${userId}`);
  }, [userId]);

  return <div>User profile</div>;
}


3] useContext() Hook

The useContext hook type is usually inferred from the initial value you passed into the createContext() function as follows.


import { useState, createContext, useContext } from "react";

// default value infered as string
const UserContext = createContext("Deepesh");

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 />
    </UserContext.Provider>
  );
}

Alternatively, you can also create an Interface or Union Type that will serve as the generic for the CreateContext return value.


import { useState, createContext, useContext } from "react";

// value can be string, number or null
type UserType = string | number  | null
const UserContext = createContext<UserType>(100);

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 />
    </UserContext.Provider>
  );
}


import { useState, createContext, useContext } from "react";

interface UserContextValue {
  user: string;
  setUser: React.Dispatch<React.SetStateAction<string>>;
}

// creating the context
const UserContext = createContext<UserContextValue | undefined>(undefined);

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    // Wrapping parent component inside Context gives
    // its children acess to value passed.
    <UserContext.Provider value={{ user, setUser }}>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 />
    </UserContext.Provider>
  );
}

function Component2() {
  return (
    <>
      <h1>Component 2</h1>
      <Component3 />
    </>
  );
}

function Component3() {
  return (
    <>
      <h1>Component 3</h1>
      <Component4 />
    </>
  );
}

function Component4() {
  return (
    <>
      <h1>Component 4</h1>
      <Component5 />
    </>
  );
}

function Component5() {
  // Accessing the created context here.
  const { user } = useContext(UserContext)!;

  return (
    <>
      <h1>Component 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  );
}

function App() {
  return (
    <>
      <Component1 />
    </>
  );
}

export default App;

/*

OUTPUT :

Hello Jesse Hall!
Component 2
Component 3
Component 4
Component 5
Hello Jesse Hall again!

*/

\

4] useRef() Hook

When using the useRef() hook we can either let Typescript infer the types or manually provide a generic/union type. The useRef hook is commonly used to reference an HTML input element we type that too.


// App.tsx

import "./App.css"
import { useRef } from "react"

export default function App() {

  // value infered as string
  const user1 = useRef("Deepesh")

  // value of type string or null
  const user2 = useRef<string|null>(null)

  // value of type html input
  const inputRef = useRef<HTMLInputElement>(null)

  return (
    <>
      <div>
        <h1> {user1.current} </h1>
        <button onClick={()=>{user1.current = user1.current+"1"}}>
        Click </button>
      </div>
    </>
  )
}



import React, { useRef } from 'react';

function App() {

  // NOTE : InitialValue is Null but React will set the DOM reference later.
  const inputRef = useRef<HTMLInputElement>(null);
  const h1Ref = useRef<HTMLHeadingElement>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);

  function handleClick1() {
    // With Optional chaining & Non-null operator
    inputRef.current?.focus(); // focus on input
    inputRef.current!.style.width = "300px";
    h1Ref.current!.innerHTML = 'Input Focused';
    buttonRef.current!.innerHTML = 'Clicked';
  }

  function handleClick2() {
    // With type guards to check null
    if (inputRef.current != null) {
      inputRef.current.focus();
      inputRef.current.style.width = "300px";
      h1Ref.current!.innerHTML = 'Input Focused';
      buttonRef.current!.innerHTML = 'Clicked';
    }
  }

  return (
    <div>
      <h1 ref={h1Ref}>Click the button to focus on the input</h1>
      <input ref={inputRef} />
      <button ref={buttonRef} onClick={handleClick1}>Focus on Input</button>
      <button ref={buttonRef} onClick={handleClick2}>Focus on Input</button>
    </div>
  );
}

export default App;


5] useMemo() & useCallback() Hooks

The useMemo() hook returns a memoized value, so the type will be inferred from the returned value or we can manually set the type using Union.


// App.tsx

import "./App.css"
import { useMemo } from "react"

export default function App() {

  const num1 = 24
  // value inferred as a number
  const result1 = useMemo(() => Math.pow(10, num1), [num1])

  const num2 = 25
  // value set as a number,string or null
  const result2 = useMemo<number|string|null>(() => Math.pow(10, num2), [num2])

  return (
    <>
      <div>
        <h1> Hello World ! </h1>
      </div>
    </>
  )
}

The useCallback() hook is kind of similar to useMemo() but returns a function instead of mutable value, so it's best to rely on typescript to infer the type, or we can apply types to the callback function being passed.


// App.tsx

import "./App.css"
import { useCallback } from "react"

function getGroundValue(num:number):string{
  return  Math.pow(10,num).toString();
}

export default function App() {

  const num1 = 24

  // value inferred ():=> number
  const result1 = useCallback(() => Math.pow(10, num1), [num1])

  // value set as ():=> number
  const result2 = useCallback<()=> number>(() => Math.pow(10, num1), [num1])

  // value set as ():=> string
  const result3 = useCallback<()=> string>(() => getGroundValue(num1), [num1])

  return (
    <>
      <div>
       <h1> Hello World ! </h1>
      </div>
    </>
  )
}


6] useReducer() Hook

When using useReducer() hook with React Typescript, we need to explicitly set the types for action and state objects, If we don't, then they will be infered as "any" type. There are various ways we can type-safe action and state objects like Literal types, Type inference or with the "typeOf" keyword.


// App.tsx

import "./App.css";
import React, { useReducer } from 'react';

// define literal types for action object
// type actionObject = {action_type:"increment"} | {action_type:"decrement"}

// define types for action object
type actionObject = {action_type: string}

// define types for state object
type stateObject = {count:number}

//------------------------------------------------------------------------------------

const initialState:stateObject = {count: 0};

function reducer(state:stateObject, action:actionObject) {
  switch (action.action_type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({action_type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({action_type: 'increment'})}>+</button>
    </>
  );
}

We can also use the Reducer Type with "< >" bracket notation to safe-type the reducer function, below is an example.


// App.tsx

import "./App.css";
import React, { useReducer, Reducer } from 'react';

type State = { count: number }
type Action = { type: 'increment' | 'decrement' }

//------------------------------------------------------------------------------------

const initialState: State = { count: 0 };

const reducer: Reducer<State, Action> = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

NOTE : If we are accepting payload inside the action object then you need to type-safe that too, below is an example.


// App.tsx

import "./App.css";
import { useReducer } from "react";

// define type for state object
type stateObject = {count:number}

// define type for action_object with literal types
type actionObject = { action_type: "increment"; payload: number }
                  | { action_type: "decrement"; payload: string };

//----------------------------------------------------------------------------------------------

const initialState = { count: 0 };

function reducer(state: stateObject, action: actionObject) {
  switch (action.action_type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}

export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ action_type: "decrement", payload: "5" })}>
        -
      </button>
      <button onClick={() => dispatch({ action_type: "increment", payload: 5 })}>
        +
      </button>
    </>
  );
}


---------------------------------------------------------------------------------------------------------------

Type HTML Events  (Useful : 1] Cheatsheet)

When using Event Listeners we often pass an event object (often refered to as "e"), to the event handler function. In Typescript we have the ability to add type to that event object.

React provides many event types which we can use, some of them are as follows :

  • React.MouseEvent
  • React.ChangeEvent
  • React.FormEvent
  • React.SyntheticEvent

If you are defining the event handler function "inline", then don't necessarily have to mention the type, since Typescript can easily infer it, below is an example.


// App.tsx

import "./App.css";

export default function App() {
  return (
    <>
     <h1> Hello World !</h1>
     <button onClick={(e)=>{
      // 'e' is inferred as MouseEvent
      console.log(e.type)
      }}> Click here too </button>
    </>
  );
}

NOTE : Most HTML events types can be inferred correctly by TypeScript, so you don’t need to explicitly set the type.

But sometimes we may define our event handlers seperately, in such cases we can't rely on Typescript Inference and need to explicitly add types, below is an example.


// App.tsx

import "./App.css";
import React from "react";

function ClickEvent1(e: React.MouseEvent): void {
  console.log(e.type)
  console.log("Hi Hello !")
}

function ClickEvent2(e: React.SyntheticEvent): void {
  console.log(e.type)
  console.log("Wassup !")
}

function handleClick(e: React.ChangeEvent<HTMLInputElement>) {
  console.log(e.type)
  console.log("Wassup !")
}

export default function App() {
  return (
    <>
      <h1> Hello World !</h1>
      <input onChange={handleClick} type="number" placeholder="Enter something..." />
      <button onClick={ClickEvent1}> Click here </button>
      <button onClick={ClickEvent2}> Click here too </button>
    </>
  );
}

NOTE : If you are unsure about event type then use "SyntheticEvent" which is the base event for all other events. You can also refer to documentation for specific event type for the corresponding event object.

TRICK : Event objects are of different types based on the event handler, If you are unsure about which Type to set, just write the event inline, hover over the "e" event object and if you are using VS CODE you'll get the hint, then you copy/paste the type from the hint to the event handler function.

---------------------------------------------------------------------------------------------------------------

Unsupported HTML Attributes

When it comes to HTML attributes in React-Typescript, not all attributes are supported. This is because React-Typescript provides its own syntax for handling attributes and elements in a way that is more optimized and streamlined for building interactive user interfaces.


// App.tsx

export default function App() {
  return (
    <>
      <div>
       <h1 align='center'> Hello World !!! </h1>                    // Does'nt Work !
       <h1 style={{ textAlign: 'center' }}> Hello World !!! </h1>  // Works !
      </div>
    </>
  )
}

Some of the HTML attributes that are not supported in React TypeScript include :

  • bgcolor : This attribute sets the background color of an element, but it's not supported in React TypeScript. Instead, you can use the style attribute to set the background color using CSS.
  • align : This attribute aligns an element, but it's not supported in React TypeScript. Instead, you can use CSS to align elements using the text-align or vertical-align properties.
  • alink : This attribute sets the color of active links, but it's not supported in React TypeScript. Instead, you can use CSS to set the color of active links using the :active pseudo-class.
  • link : This attribute sets the color of unvisited links, but it's not supported in React TypeScript. Instead, you can use CSS to set the color of unvisited links using the :link pseudo-class.
  • vlink : This attribute sets the color of visited links, but it's not supported in React TypeScript. Instead, you can use CSS to set the color of visited links using the :visited pseudo-class.

NOTE : While these attributes are not supported in React TypeScript, they are still valid HTML attributes and can be used in regular HTML documents. However, when using React TypeScript, it's generally best to use CSS to style your components and avoid using HTML attributes directly.

---------------------------------------------------------------------------------------------------------------



Comments

Popular posts from this blog

React Js + React-Redux (part-2)

React Js + CSS Styling + React Router (part-1)

ViteJS (Module Bundlers, Build Tools)