TypeScript with React: FC generics hooks
Type React components with generic props, annotate useState and useRef with explicit types, and type custom hooks that return tuples.
TypeScript with React: FC generics hooks is a free Frontend Academy lesson on CoddyKit. This is lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, and your progress syncs across the web and the CoddyKit app. The Frontend Academy course includes 4 lessons in total.
Typing Components
Modern React: type props with an interface and use a plain function. React.FC exists but is no longer recommended (implicit children, no generics support).
// Recommended:
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
function Button({ label, onClick, disabled }: ButtonProps) {
return <button onClick={onClick} disabled={disabled}>{label}</button>;
}Typing children
Use React.ReactNode for any renderable child.
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}Typing useState
useState usually infers from the initial value. Provide an explicit type when initial value is null or doesn't represent the full possible type.
const [count, setCount] = useState(0); // number
const [name, setName] = useState(''); // string
const [user, setUser] = useState<User | null>(null); // explicit
const [items, setItems] = useState<Item[]>([]); // explicit array typeTyping useRef
Two forms: ref to a DOM element (initialised to null), or a mutable container.
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null);
useEffect(() => {
inputRef.current?.focus();
timerRef.current = window.setInterval(tick, 1000);
return () => clearInterval(timerRef.current!);
}, []);Typing Event Handlers
Use React's synthetic event types.
function Form() {
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function onClick(e: React.MouseEvent<HTMLButtonElement>) {
/* ... */
}
return <form onSubmit={onSubmit}>...</form>;
}Generic Components
Components can be generic — TypeScript infers the type parameter from the call site.
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyOf: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyOf }: ListProps<T>) {
return <ul>{items.map(i => <li key={keyOf(i)}>{renderItem(i)}</li>)}</ul>;
}
<List<User>
items={users}
renderItem={u => u.name}
keyOf={u => u.id}
/>Typing Custom Hooks
Custom hooks are functions — type them like any other function. Return tuples need explicit annotation to avoid array widening.
function useToggle(initial = false): [boolean, () => void] {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle]; // tuple — annotation makes this stable
}
const [isOpen, toggle] = useToggle();Typing Context
createContext needs an initial value of the same type as the value you'll provide. Use a non-null assertion or a default object.
interface AuthContextValue {
user: User | null;
signIn: (email: string, pw: string) => Promise<void>;
signOut: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be inside AuthProvider');
return ctx;
}Typing useReducer
Type the state and the action union explicitly.
type State = { count: number };
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'set'; value: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'set': return { count: action.value };
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });Avoiding React.FC
React.FC has fallen out of favour: it implicitly adds children, doesn't support generic components cleanly, and inhibits some optimisations. Prefer plain function components with typed props.
ComponentPropsWithoutRef Utility
To inherit a native element's props (extending a button or input), use ComponentPropsWithoutRef.
type ButtonProps = React.ComponentPropsWithoutRef<'button'> & {
variant?: 'primary' | 'secondary';
};
function Button({ variant = 'primary', ...rest }: ButtonProps) {
return <button className={`btn ${variant}`} {...rest} />;
}Quick Check
Why is using React.FC generally discouraged in favour of plain typed function components?
Recap: TypeScript + React
Type props with interfaces, prefer plain functions over React.FC. children: React.ReactNode. useState often infers, annotate for null/empty arrays. useRef for DOM elements. React event types for handlers. Generic components for reusable lists/forms. Tuple return from custom hooks needs annotation. ComponentPropsWithoutRef to extend native elements.
Learn HTML with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 41
- Lessons
- 163
Frequently Asked Questions
Is the “TypeScript with React: FC generics hooks” lesson free?
Yes — the full text of “TypeScript with React: FC generics hooks” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO. The Frontend Academy course includes 4 lessons in total.
What will I learn in “TypeScript with React: FC generics hooks”?
Type React components with generic props, annotate useState and useRef with explicit types, and type custom hooks that return tuples. You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.
How long does the “TypeScript with React: FC generics hooks” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Template Literal Types
- Decorators and Metadata
- TypeScript with React: FC generics hooks
- Strict Mode and Eliminating any