Introduction
React has become one of the popular library for building user interfaces in modern web development. Its component based structure and flexibility make it easy for developers to create dynamic and high performing web applications. However, while React offers a great development experience, it doesn't include built-in type safety. This is where React with TypeScript comes in.
Why Use React with TypeScript?
While React works well with plain JavaScript, using React with TypeScript enhances the development experience. Here are some key benefits of integrating TypeScript with React:
-
Type Safety: TypeScript introduces strict typing for variables, functions, and components, helping to prevent common issues like passing incorrect data types to React components or misusing functions. This allows you to catch errors during development instead of facing them at runtime.
-
Scalability & Maintainability: In larger React applications, managing the codebase can become tough. TypeScript improves refactoring support, and its explicit types make it easier for developers to understand and collaborate on the code.
-
Better Debugging: TypeScript's static typing helps catch errors during the compile time, reducing issues at runtime.
Here is a Detailed React TypeScript Tutorial:
Setting Up TypeScript in a React Project
Vite is a modern tool that provides a fast development with minimal config. It works smoothly and fast with React and TypeScript, making it an ideal choice for developers who want a faster and more efficient workflow.
1: Install Vite and Create a New Project
To get started, we’ll need to install Vite and create a new React project. Here’s how you can do it:
- Open your terminal and run the following command :
ssh
1npm create vite@latest react-with-ts --template react-ts
- react-with-ts: This is the name of the project. You can change it to any name.
- --template react-ts: This flag specifies that you want to use the React with TypeScript template.
2: Install Dependencies
Navigate to your project directory:
ssh
1cd react-with-ts
Now, install the dependencies using :
ssh
1npm install
This will install all the necessary packages specified in the package.json
file, including React, React DOM, and TypeScript.
3: Verify TypeScript Setup
Vite’s React template comes with TypeScript support built-in, so you don’t need to manually configure TypeScript. To verify that everything is set up correctly, check the following:
- The project should have a
tsconfig.json
file in the root directory. This is the configuration file for TypeScript. - The src folder should contain TypeScript files like
App.tsx
andmain.tsx
. You should now be able to start writing TypeScript in your React components.
4: Run the Development Server
Now that everything is set up, Run the following command in the terminal:
ssh
1npm run dev
Open your browser and visit http://localhost:5173/. You should see the default Vite + React TypeScript template running.
Understanding React TypeScript Basics
TypeScript introduces static typing to your code, making it easier to spot errors and improving code maintainability. When working with React TypeScript, this enhance your development experience by adding type safety and providing helpful IDE features like auto-completion, type inference, and better refactoring support.
In this React TypeScript tutorial section, we'll go through some of the TypeScript fundamentals you'll need to use in React development.
1. Basic TypeScript Types
One of the first things to understand when using TypeScript with React is how to use basic types. You can specify the type of a variable, function argument, or return type.
Here are some of the basic types that you’ll use frequently in React components:
- string: Represents a sequence of characters.
.tsx
1let name: string = "DevWhirl";
- number: Represents any numeric value.
.tsx
1let age: number = 23;
- boolean: Represents a true or false value.
.tsx
1let isActive: boolean = true;
- array: You can define an array type using type[] or Array<type>.
.tsx
1let numbers: number[] = [1, 2, 3]; 2let names: Array<string> = ["name1", "name2", "name3"];
- object: Represents a non-primitive type (anything that is not a string, number, or boolean).
.tsx
1let user: object = { name: "Depp", age: 32 };
- any Type: The any type in TypeScript is a fallback option for situations where you're unsure of a variable's type.
.tsx
1let data: any = "Hello"; // Can be any type 2data = 123; // no error
2. Interfaces vs Types
One of the confusing concept in React TypeScript tutorial is how to define shapes of objects. In React, we use interfaces and types to define the structure of props, state, and other objects. Both interfaces and types can be used interchangeably in most cases, but there are differences.
Interfaces
-
Used to define object shapes.
-
Can be extended to create complex structures.
Example:
.tsx
1interface User { 2name: string; 3age: number; 4} 5 6const user: User = { name: "Depp", age: 34 };
Types
-
Can define union types, intersections, and more complex types.
-
Cannot be merged or extended after being declared, unlike interfaces.
Example:
.tsx
1type Color = "red" | "green" | "blue"; 2let color: Color = "red";
Working with React TypeScript Components
In a React project, components are the building blocks of your user interface. When using React TypeScript, you can make your components more type safe, and maintainable by adding types. In this React TypeScript tutorial section, we'll learn how to work with React TypeScript Components .
Functional Components in React TypeScript
Functional components are the most commonly used in modern React applications, especially with the introduction of React hooks. TypeScript allows you to define the types for props, state, and the return value of a component to ensure type safety.
Typing Props in Functional Components
Props are used to pass data from a parent component to a child component. In TypeScript, you can define the type of props using interface or type. Learn More About Props Here.
-
Using interface for Props: First, define the props type with an interface. In this example, we'll create a
Greeting
component that receives a name and age prop..tsx
1interface GreetingProps { 2name: string; 3age: number; 4} 5 6const Greeting: React.FC<GreetingProps> = ({ name, age }) => { 7return ( 8 <div> 9 <h1>Hello, {name}!</h1> 10 <p>You are {age} years old.</p> 11 </div> 12); 13}; 14 15export default Greeting;
- The GreetingProps interface defines the shape of the props object, which includes name (string) and age (number).
- The component is typed as React.FC<GreetingProps>, which means it's a functional component expecting GreetingProps as its props.
-
Using type for Props: Alternatively, you can use the type keyword instead of interface to define the props.
.tsx
1type GreetingProps = { 2name: string; 3age: number; 4}; 5 6const Greeting: React.FC<GreetingProps> = ({ name, age }) => { 7return ( 8 <div> 9 <h1>Hello, {name}!</h1> 10 <p>You are {age} years old.</p> 11 </div> 12); 13}; 14 15export default Greeting;
Both interface and type serve the same purpose here. However, interface is generally preferred for defining props because it supports declaration merging, which allows you to add additional types later.
Handling React Hooks with TypeScript
React hooks, such as useState, useEffect, useContext, and useRef, have become the foundation of functional components in React. They allow you to manage state, side effects, context, and more in functional components. TypeScript's type system enhances the React hooks experience by providing type safety, ensuring that the data you pass and return from hooks is properly validated at compile time. Learn More About useContext in React.
Here we'll explore how to use TypeScript with React hooks, such as useState, useEffect, and useRef.
1. useState in React TypeScript
The useState hook is used to declare state variables in functional components. TypeScript allows you to define the state type explicitly, making sure that the state follow to the expected type.
Basic Usage of useState with React TypeScript
In React setup, you might declare a state variable using useState, which automatically understands the type of the state based on the initial value.
.tsx
1import React, { useState } from 'react';
2
3const Counter: React.FC = () => {
4const [count, setCount] = useState<number>(0); // understands the type as number
5
6return (
7 <div>
8 <p>Count: {count}</p>
9 <button onClick={() => setCount(count + 1)}>Increment</button>
10 </div>
11);
12};
13
14export default Counter;
Here, TypeScript automatically understands that count is a number based on the initial state (0). However, if the initial state is undefined or if TypeScript cannot infer the type, you can explicitly define the state type.
Explicitly Defining the State Type
.tsx
1import React, { useState } from 'react';
2
3const Counter: React.FC = () => {
4const [count, setCount] = useState<number | null>(null); // type should be null or number
5
6return (
7 <div>
8 <p>Count: {count ?? "Loading..."}</p>
9 {/* The ?? operator is the nullish coalescing operator. If count is null
10 (which it is initially), it will display "Loading..." instead of the null value.
11 Once count is updated (from null to a number), it will display the actual count. */}
12 <button onClick={() => setCount(count === null ? 1 : count + 1)}>Increment</button>
13 </div>
14);
15};
16
17export default Counter;
In this case, we explicitly define the state type as number | null, allowing the state to start with a null value, which is useful for cases where the state is initially undefined or fetching data.
2. React TypeScript useEffect example
The useEffect hook is used to handle side effects in React, such as data fetching, DOM manipulation, or etc. With TypeScript, you can type the data that comes from side effects, such as API responses.
Basic Usage of useEffect
Here's an example of using useEffect to fetch data from an API and handle the response in React TypeScript component:
.tsx
1import React, { useState, useEffect } from 'react';
2
3interface User {
4id: number;
5name: string;
6// add all fields of the User
7}
8
9const UserProfile: React.FC = () => {
10const [user, setUser] = useState<User | null>(null);
11
12useEffect(() => {
13 // Simulating an API call
14 const fetchUser = async () => {
15 const response = await fetch('/api/user');
16 const data: User = await response.json();
17 setUser(data);
18 };
19
20 fetchUser();
21}, []);
22
23return (
24 <div>
25 {user ? <h1>{user.name}</h1> : <p>Loading...</p>}
26 </div>
27);
28};
29
30export default UserProfile;
In this example, the useEffect hook fetches user data when the component mounts. The User interface defines the expected shape of the data, and TypeScript ensures that the data fetched matches this shape.
3. useRef in React TypeScript
useRef is a hook that allows you to access DOM elements directly or store a mutable value that doesn't trigger a re-render. TypeScript makes it easier to access elements by providing type safety to the reference. Learn More About useRef Hook
Basic Usage of useRef
Here’s how to type useRef when working with DOM elements in TypeScript:
.tsx
1import React, { useRef } from 'react';
2
3const FocusInput: React.FC = () => {
4const inputRef = useRef<HTMLInputElement>(null);
5
6const focusInput = () => {
7 inputRef.current?.focus(); // Focus the input when the button is clicked
8};
9
10return (
11 <div>
12 <input ref={inputRef} type="text" />
13 <button onClick={focusInput}>Focus the input</button>
14 </div>
15);
16};
17
18export default FocusInput;
Here, we use useRef<HTMLInputElement>(null) to create a ref that points to an input element. TypeScript ensures that inputRef.current is of type HTMLInputElement, which enables type safe access to DOM properties such as focus().
Typing useRef for Non-DOM Values
You can also use useRef to store mutable values that don’t need to trigger a re-render. For example, storing the previous value of a state variable:
.tsx
1import React, { useState, useRef, useEffect } from 'react';
2
3const PreviousValue: React.FC = () => {
4const [count, setCount] = useState<number>(0);
5const prevCountRef = useRef<number>(0);
6
7useEffect(() => {
8 prevCountRef.current = count;
9}, [count]);
10
11return (
12 <div>
13 <p>Current count: {count}</p>
14 <p>Previous count: {prevCountRef.current}</p>
15 <button onClick={() => setCount(count + 1)}>Increment</button>
16 </div>
17);
18};
19
20export default PreviousValue;
In this example, we use useRef<number>(0) to store the previous value of count. Since useRef does not trigger a re-render, this is a great solution for tracking previous values across renders. A Detailed Guide about useRef
By using React hooks with TypeScript, you can take advantage of the type system to enhance the safety and maintainability of your React components. TypeScript ensures that you handle state, effects, and refs properly which reduce the risk of runtime errors.
React TypeScript Best Practices
Using with React TypeScript helps catch bugs at compile time, improves developer productivity, and enhances code maintainability. However, to fully leverage TypeScript's power in a React project, it's essential to follow best practices. Below, we'll go through the React TypeScript best practices, covering everything from component typing to state management.
- Use Explicit Types for Props in Functional Components: When defining props in React typescript components, always use TypeScript interfaces or types to ensure strong typing and better code documentation.
- Properly Type React State and Hooks: When defining state with useState, explicitly specify its type, especially if the initial state is null or undefined.
.tsx
1const [count, setCount] = useState<number>(0); 2const [user, setUser] = useState<User | null>(null); 3 4// Avoid Implicit Types That Cause any Inference 5const [count, setCount] = useState(0);
- Use useRef with Proper Typing: Always define the expected type when using useRef to interact with DOM elements. Avoid Using any for Refs:
.tsx
1const inputRef = useRef<any>(null); // Avoid any
- Use TypeScript with React Event Handlers: Typing event handlers correctly ensures that TypeScript knows what kind of event is expected.
.tsx
1const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { 2 console.log("Button clicked!", event); 3}; 4 5<button onClick={handleClick}>Click Me</button>; 6 7 // Avoid Using any for Events 8 9const handleClick = (event: any) => { 10 console.log("Button clicked!", event); 11};
Using React with TypeScript have many benefits, but following best practices ensures that your code remains maintainable, scalable, and type safe. By following these best practices, you can build fast, type safe, and production level React applications with TypeScript.
Comparing TypeScript vs JavaScript in React
Both JavaScript and TypeScript can be used to develop React applications. While JavaScript is more widely used, TypeScript provides strong typing and better developer experience, making it a popular choice for large-scale applications.
Below we will compare different aspects of TypeScript and JavaScript in React, helping you to understand their differences and decide which one to use in your projects.
1. Type Safety and Error Prevention
- JavaScript: JavaScript is a dynamically typed language, meaning variable types are not enforced. This can lead to runtime errors that are hard to debug.
- TypeScript: TypeScript enforces types at compile time, preventing errors before execution.
2. Defining React Components
- JavaScript: JavaScript React components do not enforce prop types unless PropTypes are used (which still allows runtime errors).
- TypeScript: In TypeScript, props can be strictly typed, reducing the risk of passing incorrect values.
3. Working with State and Hooks
- JavaScript: JavaScript allows any type of value in the state.
- TypeScript: In TypeScript, state is manually typed.
4. Code Scalability and Maintainability
- JavaScript: JavaScript's dynamic nature allows quick prototyping, but lack of type safety makes it harder to maintain large codebases.
- TypeScript: TypeScript makes refactoring safe through enforcing proper data structures.
Still confuse about JavaScript vs TypeScript? Learn A complete difference between them.
Which One Should You Choose?
- Choose JavaScript if:
- You are working on a small project.
- You don't need strong type safety.
- You are a beginner and want to get started with React quickly.
- Choose TypeScript if:
- You want to reduce bugs and improve maintainability.
- You are working on a large-scale application.
- You want better developer experience.
Frequently Asked Questions
-
Q: What are the benefits of using React with TypeScript?
A: TypeScript improves code quality, scalability, and maintainability by providing strong typing, and compile-time error checking.
-
Q: Is TypeScript necessary for React projects?
A: No, TypeScript is not necessary, but it is highly recommended if you are working on large scale projects or teams where type safety and maintainability are important.
-
Q: Does TypeScript impact React app performance?
A: No, TypeScript does not add any runtime overhead since it is purely a development tool that compiles down to JavaScript.
Conclusion
In short, React with TypeScript enhances code quality, improves maintainability, and reduces runtime errors by enforcing strong type safety. While JavaScript offers flexibility and a lower learning curve, TypeScript provides better development experience, scalability, and long-term benefits—especially for large applications.
In React TypeScript tutorial, we explored how to set up TypeScript in a React project, work with components, hooks, and refs, and follow best practices to write clean code.
If you're new to TypeScript, start small then gradually adding type definitions and converting JavaScript files step by step. Over time, you'll see how it improves your workflow and code reliability.
If you found this article helpful, feel free to share it with others who may benefit from it!