DEV Community

Cover image for React Mastery Series – Day 21: TypeScript with React – Building Type-Safe Applications
Siva Samanthapudi
Siva Samanthapudi

Posted on

React Mastery Series – Day 21: TypeScript with React – Building Type-Safe Applications

Welcome back to the React Mastery Series!

In the previous article, we explored Production-Ready React Architecture and learned how enterprise applications are structured using:

  • Feature-based architecture
  • Service layers
  • Custom Hooks
  • State management
  • Scalable folder organization

Today, we will explore one of the most important skills for modern React developers:

TypeScript with React

Most enterprise React applications today are built using:

React + TypeScript
Enter fullscreen mode Exit fullscreen mode

because TypeScript helps developers write:

  • Safer code
  • More maintainable applications
  • Better developer experiences
  • Fewer runtime errors

What is TypeScript?

TypeScript is a programming language built on top of JavaScript.

It adds:

  • Static typing
  • Interfaces
  • Generics
  • Type checking
  • Better IDE support

JavaScript:

let age = 30;
age = "thirty";
Enter fullscreen mode Exit fullscreen mode

JavaScript allows this.

The error appears only at runtime.


TypeScript:

let age: number = 30;
age = "thirty";
Enter fullscreen mode Exit fullscreen mode

Compiler error:

Type 'string' is not assignable to type 'number'
Enter fullscreen mode Exit fullscreen mode

The problem is detected before running the application.


Why Use TypeScript with React?

Large React applications contain:

  • Hundreds of components
  • Multiple developers
  • Complex data flows
  • API integrations

Without types:

Component
    |
    ↓
Unknown Data
    |
    ↓
Runtime Error
Enter fullscreen mode Exit fullscreen mode

With TypeScript:

Component
    |
    ↓
Typed Data
    |
    ↓
Compile-Time Validation
Enter fullscreen mode Exit fullscreen mode

Creating a React TypeScript Project

Using Vite:

npm create vite@latest my-app
Enter fullscreen mode Exit fullscreen mode

Select:

React
TypeScript
Enter fullscreen mode Exit fullscreen mode

or:

npx create-react-app my-app --template typescript
Enter fullscreen mode Exit fullscreen mode

TypeScript File Extensions

React projects use:

.js
.jsx
Enter fullscreen mode Exit fullscreen mode

for JavaScript.

TypeScript uses:

.ts
.tsx
Enter fullscreen mode Exit fullscreen mode

.tsx is used for React components.

Example:

Button.tsx
Dashboard.tsx
App.tsx
Enter fullscreen mode Exit fullscreen mode

Typing React Components

JavaScript component:

function Welcome() {
  return <h1>Hello</h1>;
}

Enter fullscreen mode Exit fullscreen mode

TypeScript component:

function Welcome(): JSX.Element {
  return <h1>Hello</h1>;
}
Enter fullscreen mode Exit fullscreen mode

The return type tells TypeScript:

"This component returns JSX."


Typing Component Props

One of the most common TypeScript use cases.

JavaScript:

function User({ name, age }) {
  return <h2>{name}</h2>;
}
Enter fullscreen mode Exit fullscreen mode

Problem:

TypeScript does not know:

  • What type is name?
  • What type is age?

Create an interface:

interface UserProps {
  name: string;
  age: number;
}
Enter fullscreen mode Exit fullscreen mode

Use it:

function User({name, age }: UserProps){
 return ( <h2> {name} - {age} </h2>);
}
Enter fullscreen mode Exit fullscreen mode

Now:

<User name="Siva" age={30} />
Enter fullscreen mode Exit fullscreen mode

works.


Incorrect:

<User name={100} age="thirty" />
Enter fullscreen mode Exit fullscreen mode

TypeScript catches the error.


Optional Props

Sometimes props are optional.

Example:

interface UserProps {
  name: string;
  age?: number;
}
Enter fullscreen mode Exit fullscreen mode

Now:

<User name="Siva"/>
Enter fullscreen mode Exit fullscreen mode

is valid.


Default Props with TypeScript

Example:

interface ButtonProps {
  text?: string;
}
Enter fullscreen mode Exit fullscreen mode

Component:

function Button({ text="Submit"}:ButtonProps){
  return (<button> {text} </button>);
}
Enter fullscreen mode Exit fullscreen mode

Typing useState

React state should also have types.

Without type:

const [count,setCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode

TypeScript automatically understands:

count → number
Enter fullscreen mode Exit fullscreen mode

For complex values:

interface User {
 id:number;
 name:string;
 email:string;
}


const [user,setUser] = useState<User | null>(null);
Enter fullscreen mode Exit fullscreen mode

Meaning:

Initially:

user = null
Enter fullscreen mode Exit fullscreen mode

Later:

user = User object
Enter fullscreen mode Exit fullscreen mode

Typing Arrays

Example:

interface Product {
 id:number;
 name:string;
 price:number;
}


const [products,setProducts] = useState<Product[]>([]);
Enter fullscreen mode Exit fullscreen mode

Now TypeScript knows:

products is an array of Product
Enter fullscreen mode Exit fullscreen mode

Typing Events

React events also need types.

Example:

Input change:

function handleChange(event: React.ChangeEvent<HTMLInputElement>){
 console.log(event.target.value);
}
Enter fullscreen mode Exit fullscreen mode

Usage:

<input onChange={handleChange} />
Enter fullscreen mode Exit fullscreen mode

Button click:

function handleClick(event:React.MouseEvent<HTMLButtonElement>){
  console.log("Clicked");
}
Enter fullscreen mode Exit fullscreen mode

Typing Forms

Example:

const handleSubmit = (event:React.FormEvent<HTMLFormElement>)=> {
 event.preventDefault();
}
Enter fullscreen mode Exit fullscreen mode

Typing useRef

Example:

Accessing input element:

const inputRef = useRef<HTMLInputElement>(null);
Enter fullscreen mode Exit fullscreen mode

Usage:

inputRef.current?.focus();
Enter fullscreen mode Exit fullscreen mode

The ? protects against null values.


Typing useReducer

Earlier we learned:

State + Action = Reducer
Enter fullscreen mode Exit fullscreen mode

With TypeScript:

State:

interface State {
 count:number;
}
Enter fullscreen mode Exit fullscreen mode

Action:

type Action = { type:"increment"} | {type:"decrement"};
Enter fullscreen mode Exit fullscreen mode

Reducer:

function reducer(state:State, action:Action):State {
  switch (action.type) {
    case "increment":
      return {
        count: state.count + 1,
      };

    case "decrement":
      return {
        count: state.count - 1,
      };

    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

Typing API Responses

Enterprise applications heavily depend on APIs.

Example response:

{
"id":101,
"name":"Siva",
"role":"Developer"
}
Enter fullscreen mode Exit fullscreen mode

Create interface:

interface Employee {
  id:number;
  name:string;
  role:string;
}
Enter fullscreen mode Exit fullscreen mode

API call:

const response:Employee = await getEmployee();
Enter fullscreen mode Exit fullscreen mode

Now components know exactly what data they receive.


Creating Reusable Generic Components

TypeScript becomes powerful with generics.

Example:

Reusable Table:

interface TableProps<T>{
 data:T[];
}
Enter fullscreen mode Exit fullscreen mode

Now the table can display:

Users:

<Table<User> data={users} />
Enter fullscreen mode Exit fullscreen mode

Products:

<Table<Product> data={products} />
Enter fullscreen mode Exit fullscreen mode

Same component.

Different data types.


Type vs Interface

Both can define structures.

Interface:

interface User {
 name:string;
 age:number;
}
Enter fullscreen mode Exit fullscreen mode

Type:

type User = {
 name:string;
 age:number;
}
Enter fullscreen mode Exit fullscreen mode

Common practice:

Use:

  • interface for objects
  • type for unions and complex types

Union Types

Example:

type Status = "loading" | "success" | "error";
Enter fullscreen mode Exit fullscreen mode

Usage:

const status:Status="loading";
Enter fullscreen mode Exit fullscreen mode

Only allowed values:

loading
success
error
Enter fullscreen mode Exit fullscreen mode

React Context with TypeScript

Example:

interface AuthContextType {
 user:User|null;
 login:()=>void;
 logout:()=>void;
}
Enter fullscreen mode Exit fullscreen mode

Create context:

const AuthContext = createContext<AuthContextType | null>(null);
Enter fullscreen mode Exit fullscreen mode

Now context usage becomes type-safe.


Enterprise Example: Banking Application

Imagine a transaction dashboard.

API response:

interface Transaction {
 id:number;
 amount:number;
 date:string;
 status: "SUCCESS"| "PENDING" | "FAILED";
}
Enter fullscreen mode Exit fullscreen mode

Component:

function TransactionList({transactions}:{transactions:Transaction[]}) {
  return (
    <>
      {transactions.map((transaction) => (
        <div>{transaction.amount}</div>
      ))}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Developers know the data structure
  • IDE provides autocomplete
  • Errors are caught early

Common Mistakes

1. Using "any" Everywhere

Avoid:

let data:any;
Enter fullscreen mode Exit fullscreen mode

Because it removes TypeScript benefits.


2. Ignoring Null Values

Incorrect:

user.name
Enter fullscreen mode Exit fullscreen mode

when user can be null.

Correct:

user?.name
Enter fullscreen mode Exit fullscreen mode

3. Over-typing Simple Values

Avoid unnecessary complexity.

Example:

const count:number = 0;
Enter fullscreen mode Exit fullscreen mode

Usually:

const count = 0;
Enter fullscreen mode Exit fullscreen mode

is enough.


TypeScript Best Practices

  • Define interfaces near the feature where they are used.
  • Avoid using any.
  • Type API responses.
  • Use reusable types.
  • Use strict mode.
  • Prefer meaningful type names.
  • Keep types organized.

TypeScript Project Structure

A scalable approach:

src

├── types
│   ├── user.ts
│   ├── api.ts
│
├── features
│   └── transactions
│       ├── components
│       ├── services
│       └── types.ts
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

Today, we learned:

✅ TypeScript adds safety to React applications.
✅ Interfaces define component contracts.
✅ Props, state, events, and APIs can be strongly typed.
✅ Generics help create reusable components.
✅ TypeScript improves maintainability in enterprise projects.
✅ React + TypeScript is the preferred stack for modern applications.


Coming Next 🚀

In Day 22, we will explore:

React State Management with Redux Toolkit – Building Enterprise Applications

We will learn:

  • Why Redux exists
  • Redux architecture
  • Store
  • Actions
  • Reducers
  • Slices
  • Redux Toolkit
  • Async API calls
  • Middleware
  • Real-world enterprise examples

Redux Toolkit is one of the most widely used state management solutions in large-scale React applications.

Happy Coding! 🚀

Top comments (0)