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

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

  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