Skip to content

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

TypeScript
1
2
3
4
// ❌ Error: Property 'name' does not exist on type '{}'
function UserProfile(props: {}) {
    return <div>{props.name}</div>; // Error here
}

Solution: Define proper prop types

TypeScript
// ✅ 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

TypeScript
// ❌ 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

TypeScript
// ✅ 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'

TypeScript
1
2
3
4
5
6
// ❌ 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

JSON
// 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

TypeScript
// ❌ 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

TypeScript
// ✅ 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

TypeScript
1
2
3
4
5
6
7
8
9
// ❌ 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

TypeScript
// ✅ 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

TypeScript
// ❌ 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

TypeScript
// ✅ 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

TypeScript
1
2
3
4
// ❌ Common children prop errors
function Container(props) { // Error: Parameter 'props' implicitly has 'any' type
    return <div>{props.children}</div>;
}

Solution: Proper children typing

TypeScript
// ✅ 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

TypeScript
1
2
3
4
5
6
7
// ❌ Recreating existing HTML props
interface ButtonProps {
    onClick: () => void;
    disabled: boolean;
    children: ReactNode;
    // Missing many standard button props
}

Solution: Extend HTML element props

TypeScript
// ✅ 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

TypeScript
// ❌ 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

TypeScript
// ✅ 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

TypeScript
// ✅ 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

  1. Always define interfaces for props, state, and API responses
  2. Use type assertions sparingly - prefer type guards
  3. Leverage utility types like Partial, Pick, Omit
  4. Enable strict mode in TypeScript configuration
  5. Use discriminated unions for complex state management
  6. Prefer composition over inheritance for component props
  7. Type your custom hooks with clear return types
  8. Handle async operations with proper error typing

External Resources