React TypeScript Errors - Complete Debugging Guide
TypeScript errors in React can be frustrating but are essential for building type-safe applications. This guide covers the most common TypeScript errors in React and their solutions.
Common TypeScript Errors in React
1. Property Does Not Exist on Type
Error: Property 'xyz' does not exist on type
// ❌ Error: Property 'name' does not exist on type '{}'
function UserProfile(props: {}) {
return <div>{props.name}</div>; // Error here
}
Solution: Define proper prop types
// ✅ Correct: Define interface for props
interface UserProfileProps {
name: string;
age?: number; // Optional property
}
function UserProfile(props: UserProfileProps) {
return <div>{props.name}</div>;
}
// Alternative: Inline type definition
function UserProfile(props: { name: string; age?: number }) {
return <div>{props.name}</div>;
}
2. Type 'undefined' is not assignable to type
Error: When dealing with optional props or state
// ❌ Error: Type 'string | undefined' is not assignable to type 'string'
interface User {
id: string;
name?: string;
}
function DisplayUser({ user }: { user: User }) {
const userName: string = user.name; // Error: name is optional
return <div>{userName}</div>;
}
Solution: Handle undefined values properly
// ✅ Option 1: Use optional chaining and nullish coalescing
function DisplayUser({ user }: { user: User }) {
const userName = user.name ?? 'Anonymous';
return <div>{userName}</div>;
}
// ✅ Option 2: Type guards
function DisplayUser({ user }: { user: User }) {
if (!user.name) {
return <div>No name provided</div>;
}
const userName: string = user.name; // Now TypeScript knows it's defined
return <div>{userName}</div>;
}
// ✅ Option 3: Non-null assertion (use carefully)
function DisplayUser({ user }: { user: User }) {
const userName: string = user.name!; // Tells TypeScript it's not undefined
return <div>{userName}</div>;
}
3. Cannot find name 'React'
Error: Cannot find name 'React'
// ❌ Error when using React 17+ with new JSX transform
import { useState } from 'react';
function MyComponent() {
return <div>Hello</div>; // Error: Cannot find name 'React'
}
Solution: Update TypeScript configuration
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx", // Use new JSX transform
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
}
}
Event Handler Type Errors
1. Event Object Types
// ❌ Common event handler errors
function MyForm() {
const handleSubmit = (event) => { // Error: Parameter implicitly has 'any' type
event.preventDefault();
};
const handleChange = (event) => { // Error: Parameter implicitly has 'any' type
console.log(event.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
</form>
);
}
Solution: Use proper event types
// ✅ Correct event types
import React, { FormEvent, ChangeEvent } from 'react';
function MyForm() {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Access form data
const formData = new FormData(event.currentTarget);
};
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
console.log(event.target.value);
};
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log('Button clicked');
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<button type="submit" onClick={handleClick}>
Submit
</button>
</form>
);
}
Hook Type Errors
1. useState with Complex Types
// ❌ Common useState errors
function UserList() {
const [users, setUsers] = useState([]); // Type is never[]
// Error: Argument of type 'User' is not assignable to parameter of type 'never'
setUsers([{ id: '1', name: 'John' }]);
return <div>{users.length}</div>;
}
Solution: Provide proper type arguments
// ✅ Correct useState typing
interface User {
id: string;
name: string;
email?: string;
}
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
// TypeScript now knows the correct types
setUsers([{ id: '1', name: 'John' }]); // ✅ Works
setSelectedUser(users[0]); // ✅ Works
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}
2. useRef Type Issues
// ❌ Common useRef errors
function FocusInput() {
const inputRef = useRef(); // Type is MutableRefObject<undefined>
const focusInput = () => {
inputRef.current.focus(); // Error: Object is possibly 'undefined'
};
return <input ref={inputRef} />;
}
Solution: Proper useRef typing
// ✅ Correct useRef typing
import { useRef, useEffect } from 'react';
function FocusInput() {
// Option 1: Allow null initially
const inputRef = useRef<HTMLInputElement | null>(null);
const focusInput = () => {
inputRef.current?.focus(); // Safe with optional chaining
};
// Option 2: Assert it will be assigned
const alwaysAssignedRef = useRef<HTMLInputElement>(null!);
useEffect(() => {
// TypeScript knows this will be HTMLInputElement
alwaysAssignedRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} />
<input ref={alwaysAssignedRef} />
<button onClick={focusInput}>Focus First Input</button>
</div>
);
}
Component Props Type Errors
1. Children Prop Issues
// ❌ Common children prop errors
function Container(props) { // Error: Parameter 'props' implicitly has 'any' type
return <div>{props.children}</div>;
}
Solution: Proper children typing
// ✅ Various ways to handle children
import { ReactNode, PropsWithChildren } from 'react';
// Option 1: Explicit children type
interface ContainerProps {
children: ReactNode;
className?: string;
}
function Container({ children, className }: ContainerProps) {
return <div className={className}>{children}</div>;
}
// Option 2: Using PropsWithChildren utility
interface BaseProps {
className?: string;
}
function Container2({ children, className }: PropsWithChildren<BaseProps>) {
return <div className={className}>{children}</div>;
}
2. Extending HTML Element Props
// ❌ Recreating existing HTML props
interface ButtonProps {
onClick: () => void;
disabled: boolean;
children: ReactNode;
// Missing many standard button props
}
Solution: Extend HTML element props
// ✅ Extend existing HTML props
import { ButtonHTMLAttributes, ReactNode } from 'react';
// Option 1: Extend HTML button props
interface CustomButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
loading?: boolean;
}
function CustomButton({ variant = 'primary', loading, children, ...props }: CustomButtonProps) {
return (
<button
{...props}
disabled={loading || props.disabled}
className={`btn btn-${variant} ${props.className || ''}`}
>
{loading ? 'Loading...' : children}
</button>
);
}
API and Async Type Errors
1. Fetch Response Types
// ❌ Untyped API responses
function UsersList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(response => response.json())
.then(data => setUsers(data)); // No type safety
}, []);
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div> // No autocomplete
))}
</div>
);
}
Solution: Type API responses
// ✅ Typed API responses
interface User {
id: string;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
function UsersList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result: ApiResponse<User[]> = await response.json();
setUsers(result.data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}
fetchUsers();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{users.map(user => (
<div key={user.id}>
{user.name} - {user.email}
</div>
))}
</div>
);
}
Advanced TypeScript Patterns
1. Discriminated Unions for Props
// ✅ Type-safe prop combinations
interface LoadingState {
status: 'loading';
}
interface SuccessState {
status: 'success';
data: User[];
}
interface ErrorState {
status: 'error';
error: string;
}
type AsyncState = LoadingState | SuccessState | ErrorState;
function UserList({ state }: { state: AsyncState }) {
switch (state.status) {
case 'loading':
return <div>Loading...</div>;
case 'success':
// TypeScript knows state.data exists here
return (
<div>
{state.data.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
case 'error':
// TypeScript knows state.error exists here
return <div>Error: {state.error}</div>;
default:
// This ensures all cases are handled
const _exhaustive: never = state;
return _exhaustive;
}
}
Best Practices Summary
- Always define interfaces for props, state, and API responses
- Use type assertions sparingly - prefer type guards
- Leverage utility types like
Partial
,Pick
,Omit
- Enable strict mode in TypeScript configuration
- Use discriminated unions for complex state management
- Prefer composition over inheritance for component props
- Type your custom hooks with clear return types
- Handle async operations with proper error typing