React and TypeScript are two of the most popular tools in modern web development. When combined, they offer a robust, type-safe, and efficient way to build complex user interfaces.
What is React?
React is a JavaScript library for building user interfaces. It's component-based, which means you break down your UI into reusable components. This makes it easier to manage and maintain your code.
What is TypeScript?
TypeScript is a superset of JavaScript that adds optional static typing. This means you can declare the types of variables, function parameters, and return values. This can help catch errors early in the development process and improve code readability.
Why use React and TypeScript together?
- Early Error Detection: TypeScript's type system can identify potential errors during development, leading to more reliable code.
- Improved Code Readability: Type annotations make code more self-documenting, enhancing code understanding and collaboration.
- Enhanced Code Reliability: Strong typing ensures that components receive the correct types of props and state, preventing unexpected behavior and crashes.
- Better IDE Support: Modern IDEs, like Visual Studio Code, provide advanced features like autocompletion, code navigation, and refactoring, which are significantly improved with TypeScript.
Basic Example of a React Component in TypeScript:
import React from "react";
interface GreetingProps {
name: string;
}
const Greeting: React.FC<GreetingProps> = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};
export default Greeting;
Key Concepts in React and TypeScript:
- Components: The building blocks of React applications. They can be functional or class-based.
- Props: Data passed from a parent component to a child component.
- State: Data that can change within a component.
- JSX: A syntax extension for JavaScript that lets you write HTML-like structures directly in your JavaScript code.
- TypeScript Types: Basic types (number, string, boolean), complex types (arrays, objects, tuples), and type inference.
Getting Started:
- Set up a React project: Use Create React App or a similar tool to quickly set up a new project.
- Install TypeScript: Run
npm install --save-dev typescript @types/react @types/react-dom
. - Configure TypeScript: Create a
tsconfig.json
file and configure the TypeScript compiler options. - Start writing TypeScript code: Write your React components in TypeScript, leveraging the benefits of strong typing.
By combining React and TypeScript, you can build robust, scalable, and maintainable web applications.
- Node.js and npm (or yarn): Ensure you have the latest versions installed.
- Visual Studio Code: Download and install the latest version.
- Open your terminal and navigate to your desired project directory.
- Initialize a new project:
Replace
npx create-vite@latest my-react-app --template react-ts
my-react-app
with your desired project name.
- Navigate to your project directory:
cd my-react-app
- Start the development server:
npm run dev
- Open VS Code and open your project folder.
While not strictly necessary, these extensions can significantly enhance your development experience:
- TypeScript Vue Plugin: Provides syntax highlighting, code completion, and other features for TypeScript in Vue files.
- ESLint: A linter to help identify and fix potential problems in your code.
- Prettier - Code formatter: Automatically formats your code according to a set of style rules.
- VSCodeVim: If you're a Vim user, this extension brings Vim keybindings to VS Code.
Now you're ready to start developing your React application using TypeScript. You can create components, manage state, and use all the powerful features of both React and TypeScript.
Type | Description | Example |
---|---|---|
Primitive Types | ||
string |
Represents text | const name: string = 'John Doe'; |
number |
Represents numerical values | const age: number = 30; |
boolean |
Represents true or false values | const isLoggedIn: boolean = true; |
null |
Represents the intentional absence of a value | const myValue: null = null; |
undefined |
Represents a variable that has been declared but not assigned a value | let myVariable: undefined; |
symbol |
Unique and immutable values | const id: symbol = Symbol('uniqueID'); |
Array Types | ||
string[] |
Array of strings | const names: string[] = ['John', 'Jane', 'Doe']; |
number[] |
Array of numbers | const numbers: number[] = [1, 2, 3, 4]; |
boolean[] |
Array of booleans | const flags: boolean[] = [true, false, true]; |
Tuple Types | ||
[string, number] |
Tuple with a string and a number | const myTuple: [string, number] = ['hello', 123]; |
Object Types | ||
{ key: value } |
Object with key-value pairs | const user: { name: string; age: number } = { name: 'John', age: 30 }; |
Interface | Defines the shape of an object | interface User { name: string; age: number; } |
Type Alias | Creates a new name for an existing type | type UserId = number; |
Union Types | Allows a variable to hold values of multiple types | `type MyType = string |
Intersection Types | Combines multiple types into a single type | type UserWithRole = User & { role: string; }; |
Conditional Types | Types that depend on other types | type IsString<T> = T extends string ? true : false; |
Generic Types | ||
<T> |
Represents a generic type parameter | const genericFunction: <T>(arg: T) => T = (arg) => arg; |
React-Specific Types | ||
React.FC |
Functional Component | const MyComponent: React.FC = () => { ... }; |
React.ComponentProps |
Extracts props type from a component | interface MyProps extends React.ComponentProps<typeof MyComponent> { ... } |
React.HTMLAttributes |
Props for HTML elements | React.HTMLAttributes<HTMLDivElement> |
React.SVGAttributes |
Props for SVG elements | React.SVGAttributes<SVGCircleElement> |
React.CSSProperties |
CSS style properties | style?: React.CSSProperties; |
React.ChangeEvent |
Event for input changes | const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { ... }; |
React.MouseEvent |
Mouse events | const handleClick = (event: React.MouseEvent) => { ... }; |
React.KeyboardEvent |
Keyboard events | const handleKeyPress = (event: React.KeyboardEvent) => { ... }; |
Additions:
symbol
: Represents unique and immutable values.Intersection Types
: Combines multiple types into a single type.Conditional Types
: Types that depend on other types.
Basic React Component in TypeScript:
import React from "react";
interface GreetingProps {
name: string;
}
const Greeting: React.FC<GreetingProps> = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};
export default Greeting;
Key Points:
- TypeScript Configuration: Vite handles TypeScript configuration for you, so you don't need to manually create a
tsconfig.json
file. - Hot Module Replacement: Vite provides fast HMR, making development efficient.
- Built-in TypeScript Support: Vite offers built-in support for TypeScript, including type checking and code completion.
- Vite's Performance: Vite is known for its fast build times and efficient development server.
1. Using Basic Data Types Directly
- You can directly use primitive data types like
string
,number
,boolean
,null
,undefined
as prop types.
Example:
import React from "react";
interface MyComponentProps {
name: string;
age: number;
isActive: boolean;
optionalValue?: string; // Optional string prop
}
const MyComponent: React.FC<MyComponentProps> = ({
name,
age,
isActive,
optionalValue,
}) => {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
<p>Is Active: {isActive ? "Yes" : "No"}</p>
{optionalValue && <p>Optional Value: {optionalValue}</p>}
</div>
);
};
export default MyComponent;
In this example:
name
is expected to be a string.age
is expected to be a number.isActive
is expected to be a boolean.optionalValue
is optional and can be a string.
2. Using Arrays and Objects
- You can also use arrays and objects as prop types.
Example:
interface MyComponentProps {
names: string[]; // Array of strings
user: {
id: number;
username: string;
};
}
const MyComponent: React.FC<MyComponentProps> = ({ names, user }) => {
// ...
};
Key Points:
- TypeScript will infer the types of props if you don't explicitly define them, but it's always a good practice to define them for better code readability and maintainability.
- Using these basic data types directly provides a clear and concise way to define the expected props for your React components.
In React TypeScript, you can define your own custom prop types using interfaces or type aliases. This allows you to create reusable and type-safe prop definitions for your components.
1. Using Interfaces
- Interfaces are a common way to define the structure and types of objects in TypeScript.
Example:
interface UserProps {
name: string;
age: number;
email: string;
}
const User: React.FC<UserProps> = ({ name, age, email }) => {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
};
In this example, the UserProps
interface defines the expected props for the User
component.
2. Using Type Aliases
- Type aliases provide another way to create custom types.
Example:
type ProductProps = {
name: string;
price: number;
description?: string; // Optional property
};
const Product: React.FC<ProductProps> = ({ name, price, description }) => {
return (
<div>
<h2>{name}</h2>
<p>Price: ${price}</p>
{description && <p>{description}</p>}
</div>
);
};
Benefits of User-Defined Prop Types:
- Improved Type Safety: Ensures that components receive the correct types of props, preventing unexpected behavior and runtime errors.
- Enhanced Code Readability: Makes the code more self-documenting and easier to understand.
- Better IDE Support: Provides better code completion, type checking, and refactoring in your IDE.
- Code Reusability: Allows you to reuse the same prop definitions across multiple components.
Example with Optional and Default Props:
interface ButtonProps {
label: string;
onClick?: () => void; // Optional function
variant?: "primary" | "secondary"; // Optional with default value
}
const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = "primary",
}) => {
return (
<button onClick={onClick} className={`button ${variant}`}>
{label}
</button>
);
};
| Category | Prop Type | Description | Example |
| ---------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------- | -------- |
| Built-in | React.HTMLAttributes<HTMLDivElement>
| Props for HTML <div>
element. Includes id
, className
, style
, event handlers, etc. | <div {...props}>...</div>
|
| Built-in | React.SVGAttributes<SVGCircleElement>
| Props for SVG <circle>
element. Includes cx
, cy
, r
, fill
, stroke
, etc. | <circle {...props} />
|
| Built-in | React.AnchorHTMLAttributes<HTMLAnchorElement>
| Props for HTML <a>
(anchor) element. Includes href
, target
, rel
, etc. | <a {...props}>...</a>
|
| Built-in | string
| String values | name: string;
|
| Built-in | number
| Numeric values | age: number;
|
| Built-in | boolean
| Boolean values (true/false) | isActive: boolean;
|
| Built-in | array
| Arrays of values (e.g., string[]
, number[]
) | names: string[];
|
| Built-in | object
| Objects with specific properties | user: { id: number; name: string; };
|
| Built-in | any
| Allows any type | value: any;
|
| User-Defined | interface MyProps
| Custom interface defining prop shapes | interface MyProps { name: string; age: number; };
|
| User-Defined | type MyProps
| Type alias defining prop shapes | type MyProps = { name: string; age: number; };
|
| User-Defined | enum MyStatus
| Defines a set of named constants | enum MyStatus { Pending, Approved, Rejected }
|
| User-Defined | union
| Combines multiple types | type MyColor = 'red' | 'green' | 'blue';
|
| User-Defined | custom type
| Creates a reusable type for complex objects | type User = { id: number; name: string; email: string; };
|
-
enum
: Defines a set of named constants. For example:enum MyStatus { Pending, Approved, Rejected, } interface MyItemProps { status: MyStatus; }
-
union
: Combines multiple types into a single type. For example:type MyColor = "red" | "green" | "blue"; interface MyBoxProps { color: MyColor; }
-
any
: Allows any type of value to be assigned to a variable. Use with caution, as it can undermine type safety. -
custom type
: Creates a reusable type for complex objects. This improves code readability and maintainability.type User = { id: number; name: string; email: string; }; interface MyUserCardProps { user: User; }
Key Differences:
- Built-in: Provided by React for common HTML/SVG elements and basic data types.
- User-Defined: Created by the developer to define custom prop structures for specific components.
Benefits of Using Prop Types:
- Type Safety: Prevents unexpected behavior and runtime errors.
- Improved Readability: Makes code more self-documenting and easier to understand.
- Better IDE Support: Enables better code completion, type checking, and refactoring.
- Maintainability: Enhances code maintainability and reduces the risk of errors.
Each user object will have two properties: name
(a string) and age
(a number).
First, we define our user type and the props type for our component. Then, we create a functional component that renders the list of users. Here's the complete example:
import React from "react";
// Define the User type
type User = {
name: string;
age: number;
};
// Define the props type for the component
type UserListProps = {
users: User[];
};
// Create the UserList component
const UserList: React.FC<UserListProps> = ({ users }) => {
return (
<div>
<h1>User List</h1>
<ul>
{users.map((user, index) => (
<li key={index}>
{user.name}, {user.age} years old
</li>
))}
</ul>
</div>
);
};
// Example usage of the UserList component
const App: React.FC = () => {
const users: User[] = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
];
return (
<div>
<UserList users={users} />
</div>
);
};
export default App;
-
User Type Definition: We define a
User
type withname
andage
properties. -
Props Type Definition: We define a
UserListProps
type that has ausers
property, which is an array ofUser
objects. -
UserList Component:
- The
UserList
component is a functional component that takesUserListProps
as its props. - It renders a list of users using the
map
function.
- The
-
App Component:
- The
App
component provides an example of how to use theUserList
component. - It creates an array of user objects and passes it to the
UserList
component as a prop.
- The
Example
-
Define the Props Type: We will define a type that uses a union of
string
andnumber
for the prop. -
Create the Component: The component will render differently based on whether it receives a string or a number.
Here's how you can do it:
import React from "react";
// Define the Prop type using a union of string and number
type DisplayProps = {
value: string | number;
};
// Create the Display component
const Display: React.FC<DisplayProps> = ({ value }) => {
// Check if value is a string or a number
if (typeof value === "string") {
return <p>The string value is: {value}</p>;
} else {
return <p>The number value is: {value}</p>;
}
};
// Example usage of the Display component
const App: React.FC = () => {
return (
<div>
{/* Pass a string */}
<Display value="Hello, World!" />
{/* Pass a number */}
<Display value={42} />
</div>
);
};
export default App;
-
DisplayProps Type Definition:
- We define a
DisplayProps
type with avalue
property that can be either astring
or anumber
(string | number
).
- We define a
-
Display Component:
- The
Display
component is a functional component that takesDisplayProps
as its props. - Inside the component, we use
typeof
to check if thevalue
prop is a string or a number. - If
value
is a string, we render it inside a<p>
tag with a message indicating it's a string. - If
value
is a number, we render it inside a<p>
tag with a message indicating it's a number.
- The
-
App Component:
- The
App
component demonstrates how to use theDisplay
component by passing both a string and a number as thevalue
prop.
- The
This example shows how to use union types in TypeScript to create flexible and type-safe React components that can handle multiple types of data.
In React, when you're using TypeScript, you often need to type the children
prop, which represents the nested elements or components that you pass to a component. Typing children
properly ensures that you get type safety and better developer experience.
Here’s a simple example of how to type the children
prop in a React component using TypeScript:
-
Define the Props Type: We will define a type for the props that includes
children
. -
Create the Component: The component will render the
children
prop.
import React, { ReactNode } from "react";
// Define the props type including children
type ContainerProps = {
children: ReactNode;
};
// Create the Container component
const Container: React.FC<ContainerProps> = ({ children }) => {
return <div className="container">{children}</div>;
};
// Example usage of the Container component
const App: React.FC = () => {
return (
<Container>
<h1>Hello, World!</h1>
<p>
This is a simple example of typing children props in a React component.
</p>
</Container>
);
};
export default App;
-
ContainerProps Type Definition:
- We define a
ContainerProps
type with achildren
property of typeReactNode
. ReactNode
is a type provided by React that represents any valid React child (strings, numbers, elements, fragments, portals, etc.).
- We define a
-
Container Component:
- The
Container
component is a functional component that takesContainerProps
as its props. - It renders the
children
prop inside a<div>
with a class of "container".
- The
-
App Component:
- The
App
component demonstrates how to use theContainer
component. - It passes
h1
andp
elements as children to theContainer
component.
- The
Typing children
helps ensure that the components you pass as children are valid React nodes, which improves type safety and developer experience. It also makes the code more readable and maintainable.
When using the useState
hook in React TypeScript, it's essential to provide type information for the state and setter function. This ensures type safety and helps prevent potential runtime errors.
Basic Usage:
import React, { useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
In this example, useState(0)
returns an array with two elements:
count
: The current state value, initially set to0
. TypeScript infers its type asnumber
.setCount
: A function to update the state. It takes a new value of typenumber
as an argument.
Explicit Typing:
While TypeScript often infers types correctly, you can explicitly declare them for better readability and maintainability:
const [count, setCount] = useState<number>(0);
Using a Tuple Type:
For more complex state scenarios, you can use a tuple type to explicitly define the types of the state and setter:
const [user, setUser] = useState<{ name: string; age: number }>({
name: "John Doe",
age: 30,
});
Key Points:
- Type Safety: Explicitly typing the state and setter helps prevent type errors and ensures correct usage.
- Readability: Clear type annotations make the code more understandable.
- IDE Support: Modern IDEs leverage type information to provide better code completion, refactoring, and error highlighting.
import React, { useState } from "react";
interface MyComponentProps {
style?: React.CSSProperties; // Optional CSS style prop
}
function MyComponent({ style }: MyComponentProps) {
const [count, setCount] = useState(0);
return (
<div style={style}>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default MyComponent;
Explanation:
-
React.CSSProperties
Interface:- This built-in interface defines the type for CSS styles in React.
- It allows you to pass a JavaScript object containing CSS properties as the
style
prop to your component.
-
MyComponentProps
Interface:- Defines the props for the
MyComponent
. style?: React.CSSProperties;
: This makes thestyle
prop optional. If not provided, the component will render with default styles.
- Defines the props for the
-
Component Usage:
- The
MyComponent
receives thestyle
prop. - The
style
prop is then applied to thediv
element using thestyle
attribute in JSX.
- The
Example Usage:
import MyComponent from "./MyComponent";
function App() {
return (
<div>
<MyComponent
style={{
backgroundColor: "lightblue",
padding: "20px",
borderRadius: "5px",
}}
/>
</div>
);
}
export default App;
In this example, the App
component passes a style object to the MyComponent
, which will then render with the specified background color, padding, and border radius.
Key Points:
React.CSSProperties
provides a type-safe way to define and pass CSS styles to your React components.
When working with events in React TypeScript, it's crucial to properly type the event handlers to ensure type safety and prevent errors. Here's how you can type events effectively:
1. Using Built-in Event Types
-
React provides built-in types for common events:
MouseEvent
: For mouse events likeonClick
,onMouseOver
,onMouseOut
, etc.KeyboardEvent
: For keyboard events likeonKeyDown
,onKeyUp
,onKeyPress
.ChangeEvent
: For input events likeonChange
(for input, textarea, select elements).FocusEvent
: For focus events likeonFocus
,onBlur
.
Example:
import React from "react";
function MyButton() {
const handleClick = (event: MouseEvent) => {
console.log(event.clientX, event.clientY); // Access mouse coordinates
};
return <button onClick={handleClick}>Click Me</button>;
}
2. Extracting Event Types from HTML Elements
- You can extract the event type from the HTML element type:
import React from "react";
function MyInput() {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.value);
};
return <input type="text" onChange={handleChange} />;
}
- This ensures that the
event
object has the correct properties for the specific HTML element.
3. Using Generic Types
- For more flexibility, you can use generics to define event handlers:
function handleGenericEvent<T extends React.BaseSyntheticEvent>(event: T) {
console.log(event.currentTarget);
}
// Usage:
<button onClick={handleGenericEvent} />;
- This function can handle any type of React event.
4. Custom Event Interfaces
- For complex events or custom events, you can define your own event interfaces:
interface MyCustomEvent extends MouseEvent {
customData: string;
}
const handleClick = (event: MyCustomEvent) => {
console.log(event.customData);
};
Key Points:
- Typing events ensures type safety and prevents unexpected behavior.
- Use the appropriate event type for each event handler.
- Consider using generics for more flexible event handling.
- Define custom event interfaces for complex or custom events.
import React, { useState } from "react";
interface FormData {
firstName: string;
lastName: string;
}
function MyForm() {
const [formData, setFormData] = useState<FormData>({
firstName: "",
lastName: "",
});
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent default form submission behavior
// Process form data here (e.g., send to server)
console.log(formData);
// Reset form (optional)
setFormData({
firstName: "",
lastName: "",
});
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="firstName">First Name:</label>
<input
type="text"
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="lastName">Last Name:</label>
<input
type="text"
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleChange}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;
Explanation:
-
FormData
Interface:- Defines the structure of the form data, ensuring type safety for the state.
-
useState
:- Initializes the
formData
state with an empty object.
- Initializes the
-
handleChange
:- Handles input changes.
- Extracts
name
andvalue
from the event target. - Updates the
formData
state using the spread syntax and dynamic property access.
-
handleSubmit
:- Handles form submission.
event.preventDefault()
prevents the default form submission behavior (page refresh).- Processes the form data (e.g., send to server).
- Optionally resets the form state.
-
JSX:
- The form elements are rendered with appropriate attributes:
name
attribute for input fields to match the state property names.value
attribute to bind input values to the state.onChange
event handler to update the state on input changes.
- The form elements are rendered with appropriate attributes:
Key Points:
- Type Safety: The use of interfaces and explicit typing ensures that the form data and event handlers are correctly typed.
- Code Readability: The code is more organized and easier to understand due to the clear type definitions.
- Maintainability: Type safety helps prevent errors and makes it easier to maintain and refactor the form.
import React, { useReducer } from "react";
interface CounterState {
count: number;
}
type CounterAction =
| { type: "increment" }
| { type: "decrement" }
| { type: "reset" }
| { type: "incrementByAmount"; amount: number };
const counterReducer = (
state: CounterState,
action: CounterAction
): CounterState => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
case "incrementByAmount":
return { count: state.count + action.amount };
default:
return state;
}
};
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
<button
onClick={() => dispatch({ type: "incrementByAmount", amount: 5 })}
>
Increment by 5
</button>
</div>
);
}
export default Counter;
Explanation:
-
Interface for CounterState:
CounterState
interface defines the shape of the state, which contains a single propertycount
of typenumber
.
-
CounterAction Union:
CounterAction
is a union type that defines all possible actions that can be dispatched to the reducer.increment
: Increases the count by 1.decrement
: Decreases the count by 1.reset
: Resets the count to 0.incrementByAmount
: Increases the count by a specified amount.
-
counterReducer Function:
- Takes the current
state
and anaction
as arguments. - Uses a
switch
statement to handle different action types. - Returns a new state object with the updated count based on the action.
- Takes the current
-
useReducer Hook:
useReducer
returns an array containing the current state and a dispatch function.- The initial state is provided as the second argument to
useReducer
.
-
Button Click Handlers:
- Dispatch the appropriate action objects to the reducer when buttons are clicked.
Key Points:
- Type Safety: The use of interfaces and unions ensures type safety for the state, actions, and reducer function.
- Code Clarity: The code is more organized and easier to understand due to the clear type definitions and action handling logic.
- Maintainability: Type safety and clear structure make the code more maintainable and less prone to errors.