Creating a TypeScript–powered React Project
Create React App (CRA) can generate a project with all TypeScript tooling preconfigured using the --template typescript flag. For faster package downloads you may switch to a mirror registry:
npm config set registry https://registry.npmmirror.com
Then bootstrap the application:
npx create-react-app my-ts-react --template typescript
If you prefer Yarn and are targeting React 19+, the command is:
yarn create react-app my-ts-react --template typescript
After creation the directory structure differs from a plain JavaScript project in three ways:
tsconfig.jsonappears at the project root, holding the TypeScript compiler options.- Component files use the
.tsxextension. - The
srcfolder containsreact-app-env.d.ts– a triple‑slash reference that loads type declarations fromreact-scripts.
/// <reference types="react-scripts" />
This declaration imports types for React, ReactDOM, Node, and static assets like images and SVGs, so you can safely import them with out additional configuration.
Understanding tsconfig.json
The tsconfig.json file dictates how the TypeScript compiler handles your code. Below is a representative configuration from a CRA‑based project:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
Important notes about compiler behavior:
- Runing
tsc hello.ts --target es6overridestsconfig.jsonbecause an input file is specified. - Executing
tscwithout arguments picks up thetsconfig.jsonfrom the project root.
It is recommended to rely on the configuration file for day‑to‑day development.
Typing React Components
React provides type definitions through the @types/react and @types/react-dom packages, which are automatically included in a CRA TypeScript template. We'll explore common typing patterns for function and class components.
Function Components
Define props with an interface or type alias. The component can be annotated using React.FC or simply by typing its parameters directly.
interface UserProfileProps {
fullName: string;
age?: number;
}
// Using React.FC generic
const UserProfile: React.FC<UserProfileProps> = ({ fullName, age }) => (
<div>Hello, I'm {fullName}, {age ?? 'unknown'} years old.</div>
);
// Alternative: inline parameter typing
const UserProfileInline = ({ fullName, age }: UserProfileProps) => (
<div>Hello, I'm {fullName}, {age ?? 'unknown'} years old.</div>
);
Provide default values through destructuring:
const UserProfileDefaultAge = ({ fullName, age = 30 }: UserProfileProps) => (
<div>Hello, I'm {fullName}, {age} years old.</div>
);
Event handlers require explicit types. A useful technique is to write the handler inline and hover over the parameter to let TypeScript infer the correct event type.
const UserProfileWithActions = ({ fullName }: UserProfileProps) => {
const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log('Button clicked', e.currentTarget);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
return (
<div>
{fullName}
<button onClick={handleButtonClick}>Like</button>
<input onChange={handleInputChange} />
</div>
);
};
Class Components
Class components can define generic parameters for props and state. The following snippet illustrates all combinations and a complete counter example.
interface CounterState {
tally: number;
}
interface CounterProps {
initialValue?: number;
}
class Counter extends React.Component<CounterProps, CounterState> {
state: CounterState = {
tally: this.props.initialValue ?? 0
};
increment = () => {
this.setState(prevState => ({ tally: prevState.tally + 1 }));
};
render() {
return (
<div>
Count: {this.state.tally}
<button onClick={this.increment}>+1</button>
</div>
);
}
}
Other common patterns:
// No props, no state
class EmptyShell extends React.Component {}
// Only props
class PropsOnly extends React.Component<CounterProps> {}
// Only state
class StateOnly extends React.Component<{}, CounterState>
Default property values can be defined via static defaultProps (though this is less common with modern TypeScript) or directly in the render logic using default destructuring as shown above.