TypeScript dodaje statyczne typowanie do JavaScriptu, dzięki czemu wiele błędów komponentów łapiesz przed uruchomieniem aplikacji. jest świetny, dopóki nie trafisz na pierwszy komponent z forwardRef, generic props, polymorphic as prop albo komunikat „type instantiation is excessively deep". W tym momencie większość developerów kopiuje rozwiązania ze Stack Overflow z 2020 roku — często przestarzałe i niezgodne z React 19. Poniżej siedem wzorców, które sam stosuję produkcyjnie i które rozwiązują te sytuacje raz a dobrze.
Wzorzec 1: ComponentProps zamiast ręcznego definiowania propów
Ręczne wypisywanie propów HTML elementu to najczęstszy błąd początkujących — i najbardziej kosztowny w utrzymaniu:
TypeScript infer'uje T automatycznie na podstawie items, więc zazwyczaj nie musisz pisać <User> explicitnie:
Code
<Select items={users} // T inferred as User getLabel={(user) => user.name} // user: User getValue={(user) => user.id} // user: User value={selectedUser} onChange={setSelectedUser}/>
Generics są nieocenione dla custom hooków, które zwracają typowane dane:
Wzorzec 4: Polymorphic component pozwala zmienić renderowany element lub komponent przez prop, często nazywany as. z as prop
Popularny wzorzec — komponent, który może renderować się jako różny element HTML albo różny komponent. Przykład: <Button as="a" href="..."> vs <Button as="button">.
Polymorphic types są notorycznie trudne w TypeScript. Najbardziej robocze rozwiązanie w 2026:
TypeScript infer'uje propsy na podstawie as — dla as="a"href jest wymagany, dla as="button" nie.
Ograniczenie: nie działa idealnie z forwardRef. Dla tego — patrz Wzorzec 6.
Wzorzec 5: satisfies dla const objects
TypeScript 4.9+ dodał operator satisfies, który jest game-changerem dla config objects:
Code
// PRZED satisfiesconst ROUTES = { home: '/', about: '/about', blog: '/blog',} as const// Problem: używaj jako stringconst path: string = ROUTES.home // OK// Ale teżROUTES.home.toLowerCase() // OK, bo 'string'// ale nie widzimy, że 'home' to literalnie '/'
Jeśli masz React 18 w projekcie, migracja na React 19 i usunięcie forwardRef to jedno z najłatwiejszych quick wins dla czytelności kodu. Jeśli jednak publikujesz bibliotekę komponentów, która ma wspierać React 18 i 19 jednocześnie, zostaw forwardRef w publicznym API do czasu, aż świadomie porzucisz React 18.
Wzorzec 7: Type-safe event handlers i eventy DOM
Najczęstszy problem — e.target.value. TypeScript często myśli, że e.target jest EventTarget (bez .value). Rozwiązanie:
Code
// ŹLE — TypeScript krzyczyfunction Input() { const handleChange = (e: Event) => { setValue(e.target.value) // Error: Property 'value' does not exist on 'EventTarget' }}// DOBRZE — typowane eventy Reactoweimport { ChangeEvent } from 'react'function Input() { const handleChange = (e: ChangeEvent<HTMLInputElement>) => { setValue(e.target.value) // OK, e.target jest HTMLInputElement } return <input onChange={handleChange} />}
Dla inline'ów TypeScript zwykle infer'uje z onChange={...}:
Code
<input onChange={(e) => setValue(e.target.value)} /> // e typed correctly
Typowe event typy:
ChangeEvent<T> — dla onChange na inputach, select'ach, textarea.
MouseEvent<T> — dla onClick, onMouseOver.
KeyboardEvent<T> — dla onKeyDown, onKeyUp.
FocusEvent<T> — dla onFocus, onBlur.
FormEvent<T> — dla onSubmit.
Gdzie T to element HTML (HTMLInputElement, HTMLButtonElement, etc.).
Dla custom event handlerów, które chcesz przekazać jako prop:
Code
type InputProps = { value: string onChange: (value: string) => void // nie event, tylko value}function Input({ value, onChange }: InputProps) { return <input value={value} onChange={(e) => onChange(e.target.value)} />}
Rozdzielenie „DOM event" od „business event" — komponent konwertuje eventy DOM na wygodniejsze API dla konsumenta.
Typy, które warto znać na skróty
TypeScript ma gotowe helpery dla typowych sytuacji w React — wiele osób pisze je od zera, nie wiedząc, że już istnieją:
Code
// Props dla childrentype Props = { children: React.ReactNode // wszystko, co się da wyrenderować}// Props dla styletype Props = { style?: React.CSSProperties // typowany style object}// Handler dla event'u z komponentutype Props = { onClick: React.MouseEventHandler<HTMLButtonElement> onSubmit: React.FormEventHandler<HTMLFormElement>}// Ref do konkretnego HTML element'uconst divRef = useRef<HTMLDivElement>(null)// Setter dla useStatetype SetValueAction<T> = React.Dispatch<React.SetStateAction<T>>// Context z properti'amiconst ThemeContext = createContext<{ theme: string setTheme: (theme: string) => void}>(null!) // null! bo wiemy, że Provider zawsze ustawi
Anti-patterns do unikania
Cztery nawyki, które najczęściej anulują wartość TypeScriptu w projekcie.
any wszędzie — tracisz całą wartość TypeScript. Jeśli nie znasz jeszcze kształtu danych, użyj unknown (musi być zawężony przed użyciem) albo wprowadź właściwy typ zamiast odkładać na później.
React.FC — społeczność odchodzi od tej adnotacji, bo niczego nie daje, a implicitnie dodaje children i utrudnia generic components. Zwykła funkcja z typowanymi propsami jest czytelniejsza i bardziej przewidywalna.
Casting as — kiedykolwiek piszesz x as SomeType, zatrzymaj się i zastanów. W 90% przypadków lepszym rozwiązaniem jest type guard lub węższy typ na początku, nie kasowanie błędu rzutowaniem.
Zbyt szerokie typy — string zamiast 'small' | 'medium' | 'large', Record<string, unknown> zamiast konkretnego obiektu. Każde zawężenie to potencjalny błąd złapany w compile time, zanim dotrze do użytkownika.
Często zadawane pytania
O autorze
Maciej Sala
Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.
Coraz więcej zespołów wraca do podejścia "web standards first". Zobacz, kiedy vanilla JS i HTMX realnie upraszczają projekt, a kiedy React nadal jest lepszym wyborem.
Siedem trendów, które w 2026 realnie zmieniają web development i marketing: GEO, zero-click search, server-first Next.js, AI-assisted development i agentic commerce.
React 19 Actions eliminują boilerplate z formularzy: useActionState zarządza stanem wysyłki, useOptimistic aktualizuje UI natychmiast, a useFormStatus synchronizuje komponenty dzieci. Praktyczne przykłady i zasady migracji.