TypeScript with React: Industry Standard UI
TypeScript and React are a match made in heaven. React handles the UI components, while TypeScript ensures the data flowing between those components is correct. This combination virtually eliminates "prop-drilling" errors and "missing property" crashes.
1. Typing Functional Components
In modern React, we use Functional Components. You can type them using React.FC (Functional Component) or by typing the props directly.
interface UserProfileProps {
name: string;
age: number;
isAdmin?: boolean; // Optional
}
// Option A: Direct typing (Recommended for simplicity)
export const UserProfile = ({ name, age, isAdmin }: UserProfileProps) => {
return <div>{name} - {age}</div>;
};
// Option B: React.FC (Includes 'children' by default in older versions)
const Card: React.FC<{ title: string }> = ({ title, children }) => (
<div className="card">
<h3>{title}</h3>
{children}
</div>
);
2. Typing Hooks
useState
TypeScript is usually good at inferring the type from the initial value. If the initial value is null or an object, you should provide a generic.
const [count, setCount] = useState(0); // Inferred as number
const [user, setUser] = useState<User | null>(null); // Generic type
useRef
Crucial for DOM elements or persisting values.
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
3. Handling Events
Typing events correctly gives you full IntelliSense for event properties (like e.target.value).
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
};
4. Complex State with useReducer
For complex state logic, useReducer combined with Discriminated Unions is the ultimate pattern.
type Action =
| { type: "increment"; amount: number }
| { type: "decrement" };
interface State { count: number; }
function reducer(state: State, action: Action): State {
switch (action.type) {
case "increment": return { count: state.count + action.amount };
case "decrement": return { count: state.count - 1 };
default: return state;
}
}
5. Typing Children and Layouts
If you create layout components, you need to type the children prop.
React.ReactNode: Most flexible. Includes strings, numbers, elements, or arrays.React.ReactElement: Only includes a single JSX element.
interface LayoutProps {
children: React.ReactNode;
}
const Layout = ({ children }: LayoutProps) => (
<main className="container">{children}</main>
);
6. Summary
- Type your Props to define component interfaces.
- Use Generics with Hooks when inference isn't enough.
- React.ChangeEvent and other built-in types make event handling easy.
- Discriminated Unions are perfect for reducers.