Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

React TypeScript Typed Props Hooks: Tutorial, Examples, FAQs & Interview Tips

What TypeScript Adds to React

TypeScript adds static typing on top of JavaScript. In a React project, that means your components, props, state, refs, events, and API responses can all be described with types. The TypeScript compiler then checks your code before it runs, which helps catch mistakes such as missing props, wrong property names, incorrect function arguments, invalid event handling, or unsafe assumptions about fetched data.

Using React with TypeScript does not change the core React model. You still build components, pass props, manage state, and use hooks in the same way. The difference is that TypeScript makes those contracts explicit. This leads to safer refactoring, better autocomplete, easier onboarding, and fewer runtime surprises.

Why Developers Use It

  • It catches common mistakes before the browser runs the app
  • It makes component props and hook return values easier to understand
  • It improves editor autocomplete and refactoring support
  • It helps teams model API data and business rules clearly
  • It reduces runtime bugs caused by incorrect assumptions

Where TypeScript Helps Most in React

AreaBenefitExample
PropsDocuments what a component expectsRequired and optional props
StateKeeps state shape predictableuseState<User | null>()
EventsGives correct event target typesReact.ChangeEvent<HTMLInputElement>
RefsMakes DOM access saferuseRef<HTMLInputElement>(null)
HooksClarifies arguments and returnsuseFetch<Product[]>()
API responsesPrevents unsafe data assumptionsApiResponse<User[]>

Example 1: Typing Component Props

Typed Props and Reusable Components
import { ReactNode } from 'react'

type ButtonVariant = 'primary' | 'secondary' | 'danger'

interface ButtonProps {
    label: string
    variant?: ButtonVariant
    disabled?: boolean
    onClick?: () => void
    children?: ReactNode
}

function Button({ label, variant = 'primary', disabled = false, onClick, children }: ButtonProps) {
    return (
        <button className={`btn btn-${variant}`} disabled={disabled} onClick={onClick}>
            {children ?? label}
        </button>
    )
}

export default Button
interface User {
    id: number
    name: string
    email: string
    role: 'admin' | 'editor' | 'user'
}

interface ProfileCardProps {
    user: User
    showEmail?: boolean
    onPromote?: (userId: number) => void
}

function ProfileCard({ user, showEmail = true, onPromote }: ProfileCardProps) {
    return (
        <article>
            <h3>{user.name}</h3>
            {showEmail && <p>{user.email}</p>}
            <p>Role: {user.role}</p>
            {onPromote && user.role !== 'admin' && (
                <button onClick={() => onPromote(user.id)}>Promote User</button>
            )}
        </article>
    )
}

export default ProfileCard
import Button from './Button'
import ProfileCard from './ProfileCard'

function App() {
    const user = {
        id: 1,
        name: 'Anita',
        email: 'anita@example.com',
        role: 'editor' as const,
    }

    return (
        <div>
            <Button label="Save" onClick={() => console.log('Saved')} />
            <ProfileCard user={user} onPromote={(id) => console.log('Promote', id)} />
        </div>
    )
}

export default App

Example 2: Typing State, Events, and Refs

Typed Hooks and Form Events
import { ChangeEvent, FormEvent, useState } from 'react'

interface LoginValues {
    email: string
    password: string
    rememberMe: boolean
}

function LoginForm() {
    const [values, setValues] = useState<LoginValues>({
        email: '',
        password: '',
        rememberMe: false,
    })
    const [error, setError] = useState<string | null>(null)

    function handleChange(event: ChangeEvent<HTMLInputElement>) {
        const { name, value, type, checked } = event.target
        setValues(current => ({
            ...current,
            [name]: type === 'checkbox' ? checked : value,
        }))
    }

    function handleSubmit(event: FormEvent<HTMLFormElement>) {
        event.preventDefault()
        if (!values.email || !values.password) {
            setError('Email and password are required.')
            return
        }
        setError(null)
        console.log(values)
    }

    return (
        <form onSubmit={handleSubmit}>
            <input name="email" value={values.email} onChange={handleChange} placeholder="Email" />
            <input name="password" type="password" value={values.password} onChange={handleChange} placeholder="Password" />
            <label>
                <input name="rememberMe" type="checkbox" checked={values.rememberMe} onChange={handleChange} />
                Remember me
            </label>
            {error && <p>{error}</p>}
            <button type="submit">Login</button>
        </form>
    )
}

export default LoginForm
import { useEffect, useRef } from 'react'

function FocusInput() {
    const inputRef = useRef<HTMLInputElement>(null)

    useEffect(() => {
        inputRef.current?.focus()
    }, [])

    return <input ref={inputRef} placeholder="Focused on mount" />
}

export default FocusInput

Example 3: Generic Hooks and API Types

Generic useFetch Hook
export interface User {
    id: number
    name: string
    email: string
    role: 'admin' | 'user'
}

export interface ApiResponse<T> {
    data: T
    message: string
    success: boolean
}
import { useEffect, useState } from 'react'

function useFetch<T>(url: string) {
    const [data, setData] = useState<T | null>(null)
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState<string | null>(null)

    useEffect(() => {
        let ignore = false

        async function load() {
            try {
                setLoading(true)
                const response = await fetch(url)
                const result = await response.json() as T
                if (!ignore) setData(result)
            } catch (err) {
                if (!ignore) setError(err instanceof Error ? err.message : 'Unknown error')
            } finally {
                if (!ignore) setLoading(false)
            }
        }

        load()
        return () => { ignore = true }
    }, [url])

    return { data, loading, error }
}

export default useFetch
import useFetch from './useFetch'
import { ApiResponse, User } from './types'

function UsersPage() {
    const { data, loading, error } = useFetch<ApiResponse<User[]>>('/api/users')

    if (loading) return <p>Loading users...</p>
    if (error) return <p>Error: {error}</p>

    return (
        <ul>
            {data?.data.map(user => (
                <li key={user.id}>{user.name} ({user.role})</li>
            ))}
        </ul>
    )
}

export default UsersPage

Common Mistakes

MistakeWhy it hurtsBetter approach
Using any too muchRemoves TypeScript safetyPrefer interfaces, unions, and generics
Ignoring nullLeads to unsafe accessUse union types like User | null
Typing everything manuallyCreates noisy codeLet inference handle simple cases
Using broad string typesAllows invalid valuesUse unions for variants and statuses

Best Practices

  • Type component props explicitly
  • Use descriptive domain models such as User, Product, and Order
  • Use generics for reusable hooks and components
  • Avoid any unless you truly have an unknown boundary
  • Model nullable values honestly
  • Let TypeScript inference reduce noise where possible

Summary

React with TypeScript gives you the same component-driven development style, but with stronger guarantees around how data flows through the application. Types make component contracts clearer, reduce runtime surprises, and make large codebases easier to refactor safely.

The most useful mindset is to treat types as contracts. A typed component clearly says what it accepts, a typed hook clearly says what it returns, and a typed API model clearly says what the application expects from the server.

Key Takeaways
  • TypeScript adds static typing to React components, hooks, refs, events, and API data.
  • Typing props makes component APIs explicit and easier to use correctly.
  • Typed state, refs, and form events reduce common bugs in interactive components.
  • Generic hooks such as useFetch can remain reusable while returning strongly typed data.
  • Union types and interfaces make React code clearer and safer to refactor.
  • Avoid overusing any because it removes the main benefit of TypeScript.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.