Strona główna / Artykuły

10 ukrytych pułapek komponentów React, które spowalniają aplikacje w erze React 19

Te powszechne błędy w komponentach React – od braku semantycznego HTML po pomijanie mechanizmu memoizacji – dlaczego React 19 i React Compiler ich nie ukrywają, wraz z rozwiązaniami.

Wydawca: REACTAPP.TOP16 min czytania3642 słów

Źródła: 10 React Component Mistakes That Are Secretly Breaking Your App in 2026

Komponenty React wydają się proste, dopóki małe błędy nie gromadzą się, powodując problemy z dostępnością, wolne renderowanie oraz trudne do wykrycia błędy. React 19, React Compiler oraz Server Components zmieniły sposób budowania aplikacji, co sprawia, że takie pomyłki są jeszcze droższe, a nie tańsze. Oto dziesięć błędów na poziomie komponentów, które potajemnie szkodzą aplikacji, wraz z rozwiązaniami dla każdego z nich.

1. Używanie <div> wszędzie zamiast semantycznego HTML

Błąd: Używanie ogólnego elementu <div> dla każdego elementu interfejsu. Nawigacja staje się <div>. Przycisk zamienia się w <div onClick={...}>. Artykuł jest otoczony <div>. Nawet etykieta formy jest traktowana w ten sam sposób.

// ❌ Bad: Div soup
function Navbar() {
  return (
    <div className="navbar">
      <div className="logo" onClick={() => router.push('/')}>Logo</div>
      <div className="nav-links">
        <div className="nav-item" onClick={() => setPage('home')}>Home</div>
        <div className="nav-item" onClick={() => setPage('about')}>About</div>
      </div>
    </div>
  );
}

Dlaczego to niszczy twoją aplikację:

  • Utrata dostępności: technologie wspomagające nie potrafią rozpoznać elementów nawigacyjnych, interaktywnych kontrolerów ani nagłówków. Osoba korzystająca z czytnika ekranu słyszy coś w rodzaju „klikalne, tekst: Strona główna” zamiast „nawigacja, link, Strona główna”.
  • Złamany dostęp klawiaturą: tagi <div> nie są domyślnie dostępne do skupienia uwagi, więc nikt korzystający z klawiatury nie może przejść do menu nawigacyjnego za pomocą klawisza Tab.
  • Słabszy SEO: silniki wyszukiwania polegają na semantycznej markupie, takim jak <nav>, <main> i <article>, aby zinterpretować strukturę strony. Strona zbudowana wyłącznie z tagów <div> jest postrzegana przez roboty jako treść niskiej jakości.
  • Komponenty serwerowe w React 19: ponieważ SSR i streaming oznaczają, że początkowy plik HTML jest tym, co widzą zarówno użytkownicy, jak i boty przed uruchomieniem procesu hydratacji, struktura semantyczna ma jeszcze większe znaczenie.

Rozwiązanie:

// ✅ Good: Semantic HTML
function Navbar() {
  return (
    <nav className="navbar" aria-label="Main navigation">
      <a href="/" className="logo">Logo</a>
      <ul className="nav-links">
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
  );
}

2026 – Najlepsze praktyki: Korzystaj z nowej API use w React 19 w połączeniu z metadanymi dotyczącymi dostępności, a także przeprowadzaj audyt swoich komponentów za pomocą narzędzi takich jak axe-core lub rozszerzenie przeglądarki WAVE. Używaj <button> dla wszystkiego, co można kliknąć, <nav> dla bloków nawigacyjnych, oraz <section> w połączeniu z aria-labelledby, gdy chcesz oznaczyć konkretny obszar strony.

2. Stylizacje wewnętrzne w JSX (upośledzanie wydajności)

Błąd: Definiowanie obiektów stylów bezpośrednio w JSX, co powoduje tworzenie zupełnie nowego odniesienia do obiektu przy każdym renderowaniu.

// ❌ Bad: New object every render
function Badge({ color, children }) {
  return (
    <span style={{ backgroundColor: color, padding: '4px 8px', borderRadius: '4px' }}>
      {children}
    </span>
  );
}

Dlaczego to niszczy twoją aplikację:

  • Dodatkowa praca z synchronizacją: ponieważ przy każdym renderowaniu tworzony jest nowy obiekt stylu, logika porównawcza Reacta wykrywa „zmienioną” właściwość i modyfikuje węzeł DOM, mimo że w rzeczywistości nic się nie zmieniło.
  • Większa wolność CSS-in-JS: biblioteki, które解析ują deklaracje stylów wewnętrznych, ponoszą ten koszt wielokrotnie, co pogłębia problem.
  • Niedoskonałości kompilatora: kompilator React 19 potrafi memetyzować komponenty, ale nie ma możliwości samodzielnej stabilizacji referencji do obiektów wewnętrznych. Gdy wykryje nowo utworzony obiekt, pomija optymalizację tej części.
  • Utracone możliwości cacheowania: przeglądarki nie mogą cacheować deklaracji stylów wewnętrznych w taki sam sposób jak pliki stylów. Klasa CSS jest解析owana raz, natomiast style wewnętrzne są ponownie解析owane przy każdym uruchomieniu komponentu.

Rozwiązanie:

// ✅ Good: CSS classes with dynamic data attributes
function Badge({ color, children }) {
  return (
    <span
      className="badge"
      data-color={color}
      style={{ '--badge-color': color }}
    >
      {children}
    </span>
  );
}
// CSS
// .badge { background-color: var(--badge-color); padding: 4px 8px; border-radius: 4px; }

Alternatywnie, narzędzia Tailwind CSS sprawdzają się dobrze, gdy są używane ostrożnie z dowolnymi wartościami:

// Acceptable: Tailwind utility classes
function Badge({ color, children }) {
  return (
    <span className="px-2 py-1 rounded" style={{ backgroundColor: color }}>
      {children}
    </span>
  );
}

2026 – Najlepsze praktyki: Umieszczaj statyczne stylizacje w modułach CSS, klasach Tailwind lub podejściu CSS-in-JS bez konieczności uruchamiania silnika. Zachowaj style wstawiane bezpośrednio do kodu dla wartości naprawdę dynamicznych, takich jak szerokość paska postępu, a nawet w takich przypadkach korzystaj z niestandardowych właściwości CSS, gdy to możliwe.

3. Niewłaściwe używanie właściwości key w listach

Błąd: Poleganie na indeksie tablicy jako kluczu, całkowite pomijanie właściwości key lub generowanie losowego klucza przy każdym renderowaniu.

// ❌ Bad: Index as key
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>{todo.text}</li>
      ))}
    </ul>
  );
}
// ❌ Worse: Random key
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={Math.random()}>{todo.text}</li>
      ))}
    </ul>
  );
}

Dlaczego to niszczy twoją aplikację:

  • Klucze oparte na indeksie: gdy elementy listy są przestawiane, React dopasowuje ten sam klucz do tej samej pozycji, w wyniku czego używa niewłaściwego węzła DOM dla niewłaściwego elementu. To powoduje pomieszanie stanu wejścia, fokusa oraz wewnętrznego stanu komponentu pomiędzy poszczególnymi elementami.
  • Klucze generowane losowo: ponieważ przy każdym renderowaniu tworzony jest nowy klucz, React rozbiera i odbudowuje każdy węzeł DOM za każdym razem, gdy następuje aktualizacja. To psuje wydajność i usuwa cały stan formularza w liście.
  • Przejścia w React 19: przy jednoczesnym renderowaniu i przejściach błędne klucze mogą powodować widoczne rozdarcia w interfejsie użytkownika oraz uszkodzony stan, gdy aktualizacja zostanie przerwana.

Rozwiązanie:

// ✅ Good: Stable, unique identifier
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

Jeśli nie ma dostępnego naturalnego, unikalnego identyfikatora, utwórz go przy pierwszym tworzeniu danych, a nie w momencie renderowania:

// At data creation, not in the component
const todos = items.map(item => ({
  ...item,
  id: crypto.randomUUID(), // or your backend's ID
}));

2026 Najlepsze praktyki: Gdy lista pochodzi z komponentu serwerowego, upewnij się, że sam serwer dostarcza stabilne identyfikatory. Unikaj wyprowadzania kluczy z pozycji w tablicy, chyba że lista jest rzeczywiście statyczna i rośnie wyłącznie poprzez dodawanie nowych elementów.

4. Bezpośrednia modyfikacja propów

Błąd: Traktowanie propów jak zmiennych lokalnych, które można modyfikować, i zmienianie ich wewnątrz komponentu potomnego.

// ❌ Bad: Mutating props directly
function SortButton({ items }) {
  const handleSort = () => {
    items.sort((a, b) => a.price - b.price); // MUTATES the prop!
    onSort(items);
  };

  return <button onClick={handleSort}>Sort by Price</button>;
}

Dlaczego to niszczy twoją aplikację:

  • Ciche uszkodzenie elementu nadrzędnego: Stan elementu nadrzędnego jest modyfikowany, a on sam nigdy o tym nie wie. Mechanizm wykrywania zmian w React opiera się na porównywaniu referencji, więc ta mutacja pozostaje całkowicie niezauważona.
  • Błędy spowodowane przestarzałym ponownym renderowaniem: Ponieważ items nadal wskazuje na tę samą tablicę w pamięci, element nadrzędny nigdy się nie renderuje ponownie, przez co interfejs pokazuje przestarzałe dane.
  • Zamieszanie kompilatora React: Kompilator zakłada, że props przepływają w drzewie aplikacji w sposób niezmienialny. Bezpośrednia modyfikacja tych wartości unieważnia jego automatyczną logikę memoizacji.
  • Utrata przewidywalności: Każdy inny komponent, który odwołuje się do tej samej tablicy, nagle zobaczy zmodyfikowaną, posortowaną wersję bez żadnego ostrzeżenia.

Rozwiązanie:

// ✅ Good: Immutable update
function SortButton({ items, onSort }) {
  const handleSort = () => {
    const sorted = [...items].sort((a, b) => a.price - b.price);
    onSort(sorted);
  };

  return <button onClick={handleSort}>Sort by Price</button>;
}

2026 – Najlepsza praktyka: korzystaj z Immer, gdy zagnieżdżone mutacje wydają się nieuniknione, a jeśli używasz TypeScript, włącz regułę ESLint taką jak prefer-readonly-props. Zakładaj, że każda właściwość jest readonly, chyba że zostanie udowodnione inaczej.

5. Zbyt wiele właściwości (zamiast kompozycji)

Błąd: tworzenie rozbudowanych „komponentów-boga”, które przyjmują tuzin lub więcej właściwości tylko po to, by kontrolować każdą możliwą wewnętrzną wariację.

// ❌ Bad: Prop explosion
function Card({
  title,
  subtitle,
  imageUrl,
  imageAlt,
  showImage,
  imagePosition,
  actions,
  showActions,
  actionAlignment,
  badge,
  badgeColor,
  onBadgeClick,
  footer,
  showFooter,
  footerContent,
  borderRadius,
  shadow,
  padding,
  variant,
}) {
  return (
    <div className={`card card--${variant}`} style={{ borderRadius, padding, boxShadow: shadow }}>
      {showImage && (
        <div className={`card__image card__image--${imagePosition}`}>
          <img src={imageUrl} alt={imageAlt} />
        </div>
      )}
      <div className="card__content">
        <h2>{title}</h2>
        {subtitle && <p>{subtitle}</p>}
      </div>
      {/* ... more conditional rendering ... */}
    </div>
  );
}

Dlaczego to niszczy twoją aplikację:

  • Kruchość interfejsu API: Każde nowe wymaganie oznacza konieczność dodania kolejnej właściwości, ingerencję w wewnętrzne mechanizmy komponentu oraz ryzyko uszkodzenia innych elementów.
  • Przesadnie duże pliki: Komponent importuje i renderuje kod odpowiedzialny za funkcje, których dany sposób użycia w ogóle nie wymaga.
  • Kłopotliwe pokrycie testami: Zmuszony jesteś weryfikować coraz większą liczbę kombinacji właściwości.
  • Problemy z React Server Components: Przeładowane listy właściwości sprawiają, że na stronie klienta trafia więcej logiki, niż jest to faktycznie konieczne.

Rozwiązanie:

// ✅ Good: Composition
function Card({ children, className }) {
  return (
    <div className={clsx('card', className)}>
      {children}
    </div>
  );
}
function CardImage({ src, alt, position = 'top' }) {
  return (
    <div className={`card__image card__image--${position}`}>
      <img src={src} alt={alt} loading="lazy" />
    </div>
  );
}
function CardContent({ title, subtitle }) {
  return (
    <div className="card__content">
      <h2>{title}</h2>
      {subtitle && <p>{subtitle}</p>}
    </div>
  );
}
function CardActions({ children, align = 'end' }) {
  return (
    <div className={`card__actions card__actions--${align}`}>
      {children}
    </div>
  );
}
// Usage
<Card>
  <CardImage src="/photo.jpg" alt="Product" />
  <CardContent title="Headphones" subtitle="Noise cancelling" />
  <CardActions>
    <button>Add to Cart</button>
  </CardActions>
</Card>

2026 – Najlepsza praktyka: skorzystaj z wzorca Compound Component lub kompozycji opartej na slotach. Niech użytkownik komponentu decyduje o jego strukturze i treści, a sam komponent powinien jedynie dostarczyć kontener.

6. Nie używanie React.Fragment (niepotrzebne Divy)

Błąd: umieszczanie elementów siostrzanych wewnątrz zwykłego <div> wyłącznie po to, by spełnić zasadę JSX, zgodnie z którą komponent musi zwracać jeden węzeł korzeniowy.

// ❌ Bad: Wrapper div for layout that breaks semantics
function TableCell({ label, value }) {
  return (
    <div> {/* This div breaks the <table> structure! */}
      <td>{label}</td>
      <td>{value}</td>
    </div>
  );
}// ❌ Bad: Extra div in a list
function ListItems({ items }) {
  return (
    <ul>
      {items.map(item => (
        <div key={item.id}> {/* Invalid HTML: div inside ul */}
          <li>{item.name}</li>
          <li>{item.description}</li>
        </div>
      ))}
    </ul>
  );
}

Dlaczego to niszczy twoją aplikację:

  • Nieważny markup: <div> nie może być prawowitym dzieckiem elementów takich jak <table>, <ul>, <select> czy <dl>. Przeglądarki w tajemnicy przepiszą DOM, aby to naprawić, co powoduje niezgodności podczas renderowania po stronie serwera.
  • Przeszkody w układzie: Dodatkowe elementy otaczające zakłócają działanie CSS Grid, Flexbox oraz właściwości gap.
  • Błędy hydratacji w React: Biorąc pod uwagę powszechność streamingu SSR i komponentów serwerowych, tego typu nieważne nawiasowanie może wywołać fatalne niezgodności hydratacji, które powodują awarię aplikacji po stronie klienta.

Rozwiązanie:

// ✅ Good: React.Fragment
function TableCell({ label, value }) {
  return (
    <>
      <td>{label}</td>
      <td>{value}</td>
    </>
  );
}
// ✅ Good: Fragment with key (required in lists)
function ListItems({ items }) {
  return (
    <ul>
      {items.map(item => (
        <React.Fragment key={item.id}>
          <li>{item.name}</li>
          <li>{item.description}</li>
        </React.Fragment>
      ))}
    </ul>
  );
}

2026 – Najlepsza praktyka: używaj skrótu <> </>, gdy nie potrzebujesz klucza, a w przypadku renderowania tablic fragmentów, które wymagają klucza, uciekaj się do <React.Fragment key={...}>. Regularnie sprawdzaj drzewo komponentów w React DevTools, aby znaleźć i usunąć niepotrzebne elementy typu div.

7. Renderowanie liczb fałszywych za pomocą && (ukryte błędy)

Błąd: poleganie na && do warunkowego renderowania czegoś, bez uwzględnienia przypadków, gdy lewy operand ma wartość 0, NaN lub pustą ścieżkę.

// ❌ Bad: Shows "0" on screen when items.length is 0
function Cart({ items }) {
  return (
    <div>
      <h1>Shopping Cart</h1>
      {items.length && (
        <ul>
          {items.map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
      )}
    </div>
  );
}
// ❌ Bad: accountBalance could be 0
function AccountSummary({ account }) {
  return (
    <div>
      {account.balance && <p>Balance: ${account.balance}</p>}
    </div>
  );
}

Dlaczego to psuje twoją aplikację:

  • React faktycznie wyświetla 0: zapisanie {0 && <Component />} nie powoduje braku wyświetlenia treści, lecz drukuje dosłowną liczbę 0 jako tekst.
  • Niewidoczne błędy interfejsu: użytkownicy muszą patrzeć na przypadkowy znak „0” gdzieś na stronie, co sprawia wrażenie błędu i może zakłócić układ strony.
  • Wartości zerowe w praktyce: takie rzeczy jak salda kont, liczba przedmiotów czy wyniki często wynoszą zero w sposób prawidłowy i oczekiwany. Traktowanie 0 jako wartości fałszywej za pomocą && oznacza, że albo nie wyświetlasz nic, gdy powinieneś, albo wyświetlasz sam cyfrę, gdy tego nie chcesz.

Rozwiązanie:

// ✅ Good: Explicit boolean check
function Cart({ items }) {
  return (
    <div>
      <h1>Shopping Cart</h1>
      {items.length > 0 && (
        <ul>
          {items.map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
      )}
    </div>
  );
}
// ✅ Good: Nullish coalescing for defaults, ternary for conditionals
function AccountSummary({ account }) {
  return (
    <div>
      {account.balance != null ? (
        <p>Balance: ${account.balance}</p>
      ) : (
        <p>Loading balance...</p>
      )}
    </div>
  );
}

2026 – Najlepsza praktyka: Unikaj całkowicie użycia && do renderowania warunkowego. Wolij strukturę ternarną (condition ? : null) lub użyj wyraźnego wartości boolowskiej w formie Boolean(count) && .... Dodaj do swojego projektu regułę ESLint, która wykrywa ryzykowne wzory && w JSX.

8. Pomijanie mechanizmu memoizacji w kosztownym JSX

Błąd: Pozwalanie na ponowne renderowanie kosztownego poddrzewa przy każdej aktualizacji rodzica, mimo że odpowiednie właściwości w rzeczywistości się nie zmieniły.

// ❌ Bad: HeavyChart re-renders every time Parent re-renders
function Dashboard({ data, filter }) {
  const [hoveredItem, setHoveredItem] = useState(null);

  return (
    <div>
      <Sidebar onHover={setHoveredItem} />
      <HeavyChart data={data} filter={filter} /> {/* Re-renders on every hover! */}
      <Tooltip item={hoveredItem} />
    </div>
  );
}
function HeavyChart({ data, filter }) {
  // Expensive SVG rendering, D3 calculations, etc.
  return <svg>{/* thousands of nodes */}</svg>;
}

Dlaczego to niszczy twoją aplikację:

  • Marnowanie zasobów obliczeniowych: HeavyChart ponownie uruchamia swoją logikę renderowania nawet wtedy, gdy data i filter pozostają niezmienione.
  • Spadki częstotliwości klatek: jeśli renderowanie HeavyChart zajmuje około 50 ms, prosty event hover na pasku bocznym może obniżyć szybkość odświeżania do około 20 fps.
  • Ograniczenia kompilatora React: automatyczna memoizacja realizowana przez kompilator działa w obrębie jednego komponentu, ale nie może optymalizować działania pomiędzy komponentami, jeśli komponent nadrzędny ciągle przekazuje nowo utworzone referencje funkcji callback.

Rozwiązanie:

// ✅ Good: Memoize the expensive component
const HeavyChart = memo(function HeavyChart({ data, filter }) {
  // Expensive rendering
  return <svg>{/* thousands of nodes */}</svg>;
});
function Dashboard({ data, filter }) {
  const [hoveredItem, setHoveredItem] = useState(null);

  // Stabilize the callback if passed to children
  const handleHover = useCallback((item) => setHoveredItem(item), []);

  return (
    <div>
      <Sidebar onHover={handleHover} />
      <HeavyChart data={data} filter={filter} />
      <Tooltip item={hoveredItem} />
    </div>
  );
}

Najlepsza praktyka na rok 2026: kompilator React 19 zmniejsza konieczność ręcznej memoizacji w prostych przypadkach, ale w przypadku naprawdę dużych poddrzew nadal warto je otoczyć funkcją memo. Użyj narzędzia Profiler w React DevTools, aby sprawdzić, gdzie znajdują się rzeczywiste wąskie gardła, zanim zdecydujesz się na stosowanie memoizacji.

9. Łączenie prezentacji z logiką biznesową

Błąd: Tworzenie jednego komponentu, który jednocześnie pobiera dane, je przekształca, śledzi stan formularza, uruchamia efekty uboczne i renderuje markup.

// ❌ Bad: God component
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [isEditing, setIsEditing] = useState(false);
  const [formData, setFormData] = useState({});

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setFormData(data);
      setLoading(false);
    });
  }, [userId]);

  const handleSubmit = async () => {
    await updateUser(userId, formData);
    setIsEditing(false);
  };

  if (loading) return <Spinner />;

  return (
    <div>
      <h1>{user.name}</h1>
      {isEditing ? (
        <form onSubmit={handleSubmit}>
          <input
            value={formData.name}
            onChange={e => setFormData({...formData, name: e.target.value})}
          />
        </form>
      ) : (
        <button onClick={() => setIsEditing(true)}>Edit</button>
      )}
    </div>
  );
}

Dlaczego to psuje twoją aplikację:

  • Trudne do testowania: nie można sprawdzić logiki pobierania danych bez uruchomienia całego komponentu, a nie można przetestować wyświetlanej interfejsu bez symulacji warstwy sieciowej.
  • Nie nadaje się do ponownego użycia: zachowanie formularza jest zamknięte w tym jednym komponencie i nie może być udostępnione gdzie indziej.
  • Słaba integracja z komponentami serwerowymi: zgodnie z konwencjami z 2026 roku pobieranie danych powinno odbywać się w komponentach serwerowych, natomiast komponenty klienckie powinny służyć wyłącznie do interaktywności.

Rozwiązanie:

// ✅ Good: Separated concerns
// Container / Logic Hook
function useUserProfile(userId) {
  const { data: user, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  const updateMutation = useMutation({
    mutationFn: updateUser,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['user', userId] }),
  });

  return { user, isLoading, updateUser: updateMutation.mutate };
}
// UI Component
function UserProfile({ userId }) {
  const { user, isLoading, updateUser } = useUserProfile(userId);
  const [isEditing, setIsEditing] = useState(false);

  if (isLoading) return <Spinner />;

  return (
    <div>
      <h1>{user.name}</h1>
      {isEditing ? (
        <UserProfileForm
          initialData={user}
          onSubmit={(data) => {
            updateUser({ userId, ...data });
            setIsEditing(false);
          }}
          onCancel={() => setIsEditing(false)}
        />
      ) : (
        <button onClick={() => setIsEditing(true)}>Edit</button>
      )}
    </div>
  );
}
// Reusable Form Component
function UserProfileForm({ initialData, onSubmit, onCancel }) {
  const [formData, setFormData] = useState(initialData);

  return (
    <form onSubmit={(e) => { e.preventDefault(); onSubmit(formData); }}>
      <input
        value={formData.name}
        onChange={e => setFormData(prev => ({...prev, name: e.target.value}))}
      />
      <button type="submit">Save</button>
      <button type="button" onClick={onCancel}>Cancel</button>
    </form>
  );
}

Zalecane podejście na rok 2026: Podzielenie obowiązków na spersonalizowane hooki do obsługi logiki, zwykłe komponenty prezentacyjne do tworzenia markupu oraz Server Components do ładowania danych. Komponent powinien albo wykonywać jakąś akcję, albo coś wyświetlać – rzadko kiedy udaje mu się dobrze pełnić obie te funkcje.

10. Nie używanie interfejsów TypeScript do definicji propów

Błąd: Poleganie na prop-types, rozproszonych komentarzach JSDoc lub, w najgorszym przypadku, całkowite pomijanie weryfikacji typów propów.

// ❌ Bad: No types
function Button(props) {
  return <button className={props.variant}>{props.children}</button>;
}
// ❌ Bad: PropTypes at runtime only
import PropTypes from 'prop-types';
function Button(props) {
  return <button className={props.variant}>{props.children}</button>;
}
Button.propTypes = {
  variant: PropTypes.string,
  children: PropTypes.node,
};

Dlaczego to niszczy twoją aplikację:

  • Błędy bez objawów: błędy ortograficzne w nazwach właściwości, brak wymaganych wartości oraz niezgodne typy powodują awarie w trakcie działania aplikacji, zamiast być wykryte przed jej wdrożeniem.
  • Refaktoryzacja staje się trudna: zmiana nazwy pojedynczej właściwości zmusza do ręcznego przeszukiwania całego kodu, podczas gdy system typów natychmiast wskazuje wszystkie miejsca w kodzie, które wymagają aktualizacji.
  • Gorsze doświadczenie programisty: bez typów tracimy funkcję autodopisywania, wskazówki dokumentacyjne w tekście oraz zabezpieczenia zapewniające automatyczne zmiany nazw.
  • Słaba zgodność z React 19: nowsze funkcje React, takie jak Server Components, używanie use z Promises oraz przekierowywanie referencji, są projektowane z myślą o modelu typów TypeScript, więc pominięcie typów oznacza utratę gwarancji z czasu kompilacji, na których te funkcje polegają.

Rozwiązanie:

// ✅ Good: Explicit TypeScript interface
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
  type?: 'button' | 'submit' | 'reset';
}
function Button({
  variant = 'primary',
  size = 'md',
  disabled = false,
  onClick,
  children,
  type = 'button'
}: ButtonProps) {
  return (
    <button
      type={type}
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Zalecane podejście na rok 2026: Włącz tryb ścisły (strict: true w pliku tsconfig.json). Definiuj właściwości modeli za pomocą deklaracji interface zamiast aliasów type, ponieważ interfejsy zazwyczaj generują jaśniejsze komunikaty o błędach i są łatwiejsze do rozszerzania. Użyj React.ComponentProps, gdy potrzebujesz wywodzić typy z rdzennych elementów HTML podczas tworzenia komponentów otaczających. W żadnym wypadku unikaj używania any do typów właściwości.

Podsumowanie: Lista kontrolna komponentów na 2026 rok

Zanim połączysz komponent, przejrzyj tę listę kontrolną:

  • [ ] Czy wybrałeś semantyczny HTML zamiast stosowania ogólnych elementów <div>?
  • [ ] Czy style są zdefiniowane w CSS, a nie w obiektach stylów wewnątrz elementu?
  • [ ] Czy elementy listy wykorzystują stabilne, unikalne wartości key?
  • [ ] Czy wszystkie właściwości są zawsze traktowane jako tylko do odczytu?
  • [ ] Czy komponent może być składany, zamiast wymagać ponad tuzina właściwości konfiguracyjnych?
  • [ ] Czy zastąpiłeś niepotrzebne elementy wrapper div Fragment?
  • [ ] Czy renderowanie warunkowe jest realizowane za pomocą operacji ternarnej lub wyraźnych sprawdzeń logicznych, zamiast &&?
  • [ ] Czy kosztowny JSX jest memoizowany tam, gdzie to ma znaczenie?
  • [ ] Czy logika biznesowa jest oddzielona od markupu i znajduje się w hookach lub funkcjach pomocniczych?
  • [ ] Czy wszystkie właściwości są w pełni opisane za pomocą interfejsów TypeScript?

Żaden z tych błędów nie spowoduje natychmiastowego załamania się twojej aplikacji od pierwszego dnia. Zamiast tego stopniowo niszczą ją rosnący dług w zakresie dostępności, powolna utrata wydajności oraz subtelne uszkodzenia stanu aplikacji. Rozwiązanie tych problemów już teraz oznacza, że twoja baza kodu React będzie funkcjonować poprawnie podczas kolejnego aktualizacji frameworka, kolejnej dużej refaktoryzacji oraz przez cały czas, gdy inna zespół przejmie konserwację twojego projektu.