π¦ React Typing Props & State β Write Safer Components with TypeScript (2025 Guide)
π§² Introduction β Why Type Props & State in React?
In React.js, props and state are essential for component behavior and rendering. By using TypeScript, you bring type safety to these data structures, helping:
- π Prevent runtime bugs
- π§ Enable powerful IntelliSense/autocomplete
- β Document component APIs with explicit typings
Whether you’re working with form data, UI toggles, or complex nested props, typing them ensures your components are predictable, maintainable, and scalable.
π― In this guide, youβll learn:
- How to type props and state in functional components
- Use optional, union, and nested props
- Type
useState, event handlers, and object states - Best practices for reusable prop types
π§± 1. Typing Props in Functional Components
β Example: Basic Props
type GreetingProps = {
name: string;
age?: number; // optional
};
const Greeting = ({ name, age }: GreetingProps) => (
<p>Hello, {name}! {age && `(Age: ${age})`}</p>
);
π GreetingProps defines what data the component expects
β
Helps catch prop misuse early
β
With React.FC (FunctionComponent)
const Greeting: React.FC<GreetingProps> = ({ name, age }) => (
<p>Hello, {name}! {age}</p>
);
β οΈ React.FC automatically types children but adds overheadβprefer plain function types for performance-critical code.
π 2. Typing Component State with useState
β Simple State:
const [count, setCount] = useState<number>(0);
β Object State:
type FormState = {
username: string;
email: string;
};
const [form, setForm] = useState<FormState>({
username: '',
email: '',
});
β
Keep object state type-safe
π Type useState<T>() to define the structure
π¦ 3. Optional & Default Props
type ButtonProps = {
label: string;
variant?: 'primary' | 'secondary';
};
const Button = ({ label, variant = 'primary' }: ButtonProps) => (
<button className={variant}>{label}</button>
);
β
Use ? for optional props
β
Provide default values in the function body
π§ 4. Event Handlers Typing
β Input Change Event:
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
β Button Click Event:
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log('Clicked!', e.currentTarget);
};
π Use React.Event, React.ChangeEvent, React.MouseEvent, etc.
π§© 5. Typing Props with Children
type CardProps = {
title: string;
children: React.ReactNode;
};
const Card = ({ title, children }: CardProps) => (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
β
React.ReactNode supports text, elements, fragments, arrays, etc.
π 6. Reusable Prop Patterns with Union & Enums
β Union Props:
type AlertProps =
| { type: 'success'; message: string }
| { type: 'error'; errorCode: number };
const Alert = (props: AlertProps) => {
if (props.type === 'success') return <p>{props.message}</p>;
return <p>Error Code: {props.errorCode}</p>;
};
β Enum Props:
enum Variant {
Primary = 'primary',
Secondary = 'secondary',
}
type ButtonProps = {
label: string;
variant: Variant;
};
π Use union types for conditional rendering
β
Use enums when values are shared across components
π§ͺ 7. Typing Props for Custom Hooks
type ToggleHook = [boolean, () => void];
function useToggle(initial: boolean): ToggleHook {
const [on, setOn] = useState(initial);
const toggle = () => setOn(prev => !prev);
return [on, toggle];
}
β
Clear return types enhance reusability
β
Useful when sharing state logic across multiple components
π Best Practices
| Practice | Why It Helps |
|---|---|
β
Use type or interface | Makes components self-documenting |
β
Always annotate useState for non-primitives | Prevents inference issues |
| β Use union types for conditional props | Enforces stricter usage |
| β Keep prop types small & reusable | Improves readability and reuse |
β
Avoid any unless absolutely needed | Maintain type safety and clarity |
π Summary β Recap & Next Steps
Typing props and state in React with TypeScript helps you build stable, self-documenting components. You gain better autocomplete, validation, and early error detectionβall of which help scale your project with confidence.
π Key Takeaways:
- Use
type/interfaceto define prop and state shapes - Annotate
useStatewhen dealing with objects, nulls, or arrays - Use
React.ChangeEvent,React.MouseEventfor event handlers - Pass children using
React.ReactNode - Use union types or enums for component variants
βοΈ Real-World Relevance:
Used in large TypeScript codebases like Shopify Polaris, Radix UI, and Microsoft Fabric to deliver type-safe and reusable components.
β FAQ Section
β What’s the difference between type and interface?
β
Both can define shapes. Prefer type for union/intersections and interface for extending object contracts.
β Do I need to type state if it’s a number or string?
β
Not always. TypeScript can infer simple types, but use explicit types for objects or when initializing to null.
β Can I type default props?
β
Yes. Provide defaults in the function body and mark them as optional (prop?: string).
β How do I avoid repeating prop types in multiple files?
β
Export shared types into a /types folder and import as needed:
import { ButtonProps } from '@/types/button';
β How to type children correctly in React?
β
Use React.ReactNode or ReactElement depending on flexibility:
type Props = { children: React.ReactNode };
Share Now :
