Tutorials Logic, IN info@tutorialslogic.com

React TypeScript Typed Props Hooks

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

Area Benefit Example
Props Documents what a component expects Required and optional props
State Keeps state shape predictable useState<User | null>()
Events Gives correct event target types React.ChangeEvent<HTMLInputElement>
Refs Makes DOM access safer useRef<HTMLInputElement>(null)
Hooks Clarifies arguments and returns useFetch<Product[]>()
API responses Prevents unsafe data assumptions ApiResponse<User[]>

Example 1: Typing Component Props

Typed Props and Reusable Components

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

Example 1: Typing Component Props - TSX Example

Example 1: Typing Component Props - TSX Example
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

Example 1: Typing Component Props - TSX Example 2

Example 1: Typing Component Props - TSX Example 2
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

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

Example 2: Typing State, Events, and Refs - TSX Example

Example 2: Typing State, Events, and Refs - TSX Example
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

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
}

Example 3: Generic Hooks and API Types - TSX Example

Example 3: Generic Hooks and API Types - TSX Example
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

Example 3: Generic Hooks and API Types - TSX Example 2

Example 3: Generic Hooks and API Types - TSX Example 2
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

Typescript Failure Cases

Mistake Why it hurts Better approach
Using any too much Removes TypeScript safety Prefer interfaces, unions, and generics
Ignoring null Leads to unsafe access Use union types like User | null
Typing everything manually Creates noisy code Let inference handle simple cases
Using broad string types Allows invalid values Use 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.

Before you move on

React TypeScript Typed Props Hooks Mastery Check

5 checks
  • 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.
  • 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.
Browse Free Tutorials

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