Table of Contents
- Introduction
- Prerequisites
- Step 1: Set Up a New React Project with TypeScript
- Step 2: Install Required Dependencies
- Step 3: Set Up Folder Structure
- Step 4: Create Functional Components with TypeScript
- Step 5: Props and State in TypeScript
- Step 6: Handling Events with TypeScript in React
- Step 7: Working with Context API and Redux (Optional)
- Step 8: TypeScript Types for React Router
- Step 9: Type Safety in useEffect and useState Hooks
- Step 10: Conclusion
Introduction
React, a popular JavaScript library for building user interfaces, works seamlessly with TypeScript. TypeScript adds type safety to React components, making development more robust, easier to maintain, and less error-prone. By combining React and TypeScript, developers can enhance their workflow, improve code readability, and catch potential bugs during the development phase.
In this guide, we will show how to set up and work with React and TypeScript, step-by-step, covering various aspects such as component creation, state management, event handling, and more.
Prerequisites
Before getting started, ensure you have the following tools installed:
- Node.js: Install from Node.js website.
- npm (Node Package Manager): This comes with Node.js and is used to install dependencies.
You should also have some familiarity with the basics of React and TypeScript.
Step 1: Set Up a New React Project with TypeScript
The first step is to create a new React project using TypeScript. You can easily set up a React and TypeScript project using Create React App:
npx create-react-app my-react-ts-app --template typescript
This will set up a new React application pre-configured with TypeScript.
Once the installation is complete, navigate to your project directory:
cd my-react-ts-app
Step 2: Install Required Dependencies
You should already have most of the required dependencies installed by Create React App, but here are some additional packages you may need:
- React Router for handling routes (if building a multi-page app).
- Redux or Context API for state management.
For React Router:
npm install react-router-dom
npm install @types/react-router-dom --save-dev
For Redux (if needed for state management):
npm install react-redux @reduxjs/toolkit
npm install @types/react-redux --save-dev
Step 3: Set Up Folder Structure
Create the basic folder structure for better organization. In a large project, it’s a good practice to organize your project into components, assets, and other necessary directories.
Example folder structure:
src/
├── assets/
├── components/
├── pages/
├── services/
├── App.tsx
└── index.tsx
This will help in keeping the project modular and scalable as it grows.
Step 4: Create Functional Components with TypeScript
React components can be written as functional components with TypeScript. Here’s an example of creating a simple Button component:
src/components/Button.tsx:
import React from 'react';
// Define type for props
type ButtonProps = {
label: string;
onClick: () => void;
};
const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
return (
<button onClick={onClick}>{label}</button>
);
};
export default Button;
In this example:
- ButtonProps defines the expected properties (
label
andonClick
). - React.FC (Function Component) automatically infers
children
and enforces the type safety for props.
Step 5: Props and State in TypeScript
To handle props and state in TypeScript, you can explicitly define types for the state and props for your components.
Example with State:
src/components/Counter.tsx:
import React, { useState } from 'react';
// Define type for state
type CounterState = {
count: number;
};
const Counter: React.FC = () => {
const [state, setState] = useState<CounterState>({ count: 0 });
const increment = () => setState({ count: state.count + 1 });
const decrement = () => setState({ count: state.count - 1 });
return (
<div>
<h1>{state.count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
export default Counter;
Here:
- The state (
count
) is typed withCounterState
. - TypeScript ensures that you only set the
state
object with acount
property of typenumber
.
Step 6: Handling Events with TypeScript in React
When dealing with events like onClick
, onChange
, or onSubmit
, TypeScript can provide type safety. Below is an example of handling a form submission event.
Example:
import React, { useState } from 'react';
const Form: React.FC = () => {
const [name, setName] = useState<string>('');
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
console.log('Submitted name:', name);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
};
export default Form;
Here, React.FormEvent
ensures that handleSubmit
handles the event correctly with type safety.
Step 7: Working with Context API and Redux (Optional)
If your app needs to manage state globally, you can use React Context API or Redux. Below is a simple example of using the Context API for global state management.
Example (Context API):
- Create Context:
import React, { createContext, useState, useContext } from 'react';
interface ThemeContextType {
theme: string;
setTheme: (theme: string) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC = ({ children }) => {
const [theme, setTheme] = useState<string>('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
- Using Context in a Component:
import React from 'react';
import { useTheme } from './ThemeContext';
const ThemeSwitcher: React.FC = () => {
const { theme, setTheme } = useTheme();
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
};
export default ThemeSwitcher;
Step 8: TypeScript Types for React Router
When using React Router, you can add type definitions for route parameters and queries.
Example:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, useParams } from 'react-router-dom';
interface UserParams {
userId: string;
}
const User: React.FC = () => {
const { userId } = useParams<UserParams>();
return <div>User ID: {userId}</div>;
};
const App: React.FC = () => {
return (
<Router>
<Switch>
<Route path="/user/:userId" component={User} />
</Switch>
</Router>
);
};
export default App;
In this example, useParams<UserParams>
provides type safety for route parameters.
Step 9: Type Safety in useEffect and useState Hooks
TypeScript also adds type safety to useEffect
and useState
hooks.
Example (useEffect with Fetch):
import React, { useState, useEffect } from 'react';
interface Data {
id: number;
name: string;
}
const FetchData: React.FC = () => {
const [data, setData] = useState<Data | null>(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result: Data = await response.json();
setData(result);
};
fetchData();
}, []);
return (
<div>
{data ? <p>{data.name}</p> : <p>Loading...</p>}
</div>
);
};
export default FetchData;
Here, the Data
interface ensures the fetched data matches the expected type.
Step 10: Conclusion
You’ve now learned how to connect a React frontend with TypeScript, including:
- Setting up a React app with TypeScript.
- Creating typed functional components with props and state.
- Handling events and managing global state with Context API or Redux.
- Ensuring type safety when working with React Router, useEffect, and useState.
By combining React and TypeScript, you can improve your code quality, catch errors early, and make your React applications more maintainable and scalable. With these techniques, you’re well-equipped to develop large-scale, type-safe React applications.