π¦ 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
/interface
to define prop and state shapes - Annotate
useState
when dealing with objects, nulls, or arrays - Use
React.ChangeEvent
,React.MouseEvent
for 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 :