Powrot do bloga
20 sierpnia 2026
published

Napisałem grę w React bez AI, żeby w końcu zrozumieć własny frontend

Od kilku miesięcy dowoziłem frontendy, których do końca nie rozumiałem. Działały, klient był zadowolony, a ja miałem z tyłu głowy nieprzyjemne uczucie, że...

Okladka: Napisałem grę w React bez AI, żeby w końcu zrozumieć własny frontend

Od kilku miesięcy dowoziłem frontendy, których do końca nie rozumiałem. Działały, klient był zadowolony, a ja miałem z tyłu głowy nieprzyjemne uczucie, że gdyby to się wysypało w nietypowym miejscu, siedziałbym nad tym godzinami. Więc odłożyłem AI na bok i napisałem grę przeglądarkową od zera.

Czytanie: ~8 min

Backend znam, front dowoziłem na wiarę

Mój główny stack to .NET. C#, API, SQL, integracje, kilka lat pracy na backendzie w firmie z sektora outsourcingu finansowego. Tam wiem, co robię: widzę zapytanie, widzę plan wykonania, wiem gdzie szukać wycieku pamięci i dlaczego endpoint zwraca 500 zamiast 400.

Frontend przez lata był dla mnie strefą przygraniczną. Umiałem sklepać widok, ogarnąć CSS, podpiąć fetch. Ale React zawsze traktowałem jako coś, co "jakoś działa".

Potem przyszło AI i ta granica zniknęła w ciągu kilku miesięcy. Claude Code, Cursor, Codex. Wpisujesz, co chcesz zobaczyć, dostajesz komponent. Poprawiasz promptem, dostajesz lepszy komponent. Aplikacja rośnie, deploy idzie, wszystko się kręci.

Problem pojawił się w momencie, w którym musiałem coś w tym kodzie zmienić samodzielnie. Nie dopisać nowej funkcji promptem, a zrozumieć, dlaczego dane w jednym miejscu odświeżają się o jeden krok za późno. Wtedy zorientowałem się, że czytam własne repozytorium jak dokumentację obcej biblioteki.

Dlaczego vibe coding przestał mi wystarczać

Nazwijmy to po imieniu: pisanie kodu, którego nie rozumiesz, jest zaciąganiem długu technicznego z odsetkami płatnymi w najgorszym możliwym momencie.

Dopóki projekt jest mały, nie boli. AI wygeneruje, AI naprawi, AI przepisze. Trzy ekrany, jeden formularz, lista z API. Nie musisz wiedzieć, jak React decyduje o ponownym renderze, bo nic się jeszcze nie rozjeżdża.

Ból zaczyna się przy dwóch rzeczach.

Pierwsza to debugowanie czegoś, co AI zrobiło poprawnie składniowo, ale niepoprawnie logicznie. Model nie ma dostępu do tego, jak Twoja aplikacja ma się zachować w brzegowym przypadku, o którym nie napisałeś w promptcie. Kod się kompiluje, testy przechodzą, a użytkownik zgłasza, że po odświeżeniu strony znikają mu ustawienia.

Druga to review i decyzje architektoniczne. Jeżeli nie wiesz, czym różni się trzymanie stanu w komponencie od wyniesienia go wyżej, nie umiesz ocenić, czy propozycja AI Cię nie zabetonuje na przyszłość. Przyjmujesz każdą sugestię, bo brak Ci punktu odniesienia.

I trzecia rzecz, o której mówi się mniej, a która dla mnie jako osoby pracującej na kontrakcie ma znaczenie czysto zawodowe. Na rozmowie technicznej nikt nie zapyta Cię, jak dobrze promptujesz. Zapytają, jak działa hook, dlaczego lista wymaga stabilnego klucza i co się stanie, jeśli w efekcie zapomnisz o tablicy zależności.

Nie mam nic przeciwko AI w kodowaniu. Sam z niego korzystam codziennie i wcale nie planuję przestać. Ale AI jest wzmacniaczem tego, co już umiesz, a nie protezą tego, czego nie umiesz. Wzmacniacz podpięty do zera nadal daje zero.

Wróciłem do podstaw i przeszedłem cały kurs

Zamiast szukać skrótu, kupiłem porządny kurs Reacta i przeszedłem go w całości. Nie wybiórczo, nie "przeskoczę tę sekcję, bo już wiem". Od pierwszego modułu do ostatniego.

Zakres wyglądał tak:

  • JSX i to, że pisząc znacznik w JavaScripcie, tak naprawdę wywołujesz funkcję, która buduje opis interfejsu
  • komponenty i props jako sposób podziału widoku na kawałki z jasno określonym wejściem
  • stan i hooki - useState, useEffect, useRef, do tego useReducer dla stanu, który ma więcej niż dwa pola
  • kontekst i moment, w którym przekazywanie props przez pięć poziomów przestaje mieć sens
  • routing i budowanie aplikacji wielowidokowej bez przeładowania strony
  • praca z API - pobieranie danych, obsługa stanu ładowania i błędów, formularze z walidacją
  • wydajność - memoizacja, klucze na listach, unikanie renderów, których nikt nie potrzebuje

Najwięcej dała mi ta część, którą wcześniej najbardziej ignorowałem: cykl życia i to, kiedy dokładnie React przelicza widok. Jako backendowiec myślałem o stanie aplikacji jak o rekordzie w bazie. Ustawiasz wartość, wartość jest ustawiona, jedziemy dalej. React działa inaczej - deklarujesz, jak ma wyglądać interfejs dla danego stanu, a framework sam decyduje, kiedy i co przerysować.

Do momentu, w którym to zrozumiałem, każdy nieoczekiwany render był dla mnie magią. Po kursie stał się konsekwencją czegoś, co sam napisałem.

Space Clicker - projekt, w którym stan aplikacji jest całą grą

Kurs bez projektu jest jak przeczytana książka o pływaniu. Dlatego równolegle zbudowałem coś własnego: Space Clicker, prostą grę przeglądarkową w JavaScript i React. Działa też jako PWA, więc da się ją zainstalować na telefonie i odpalić jak zwykłą aplikację.

Zasady są banalne. Klikasz i zbierasz Stellarium, kosmiczną walutę gry. Za Stellarium odblokowujesz kolejne jednostki swojej floty, a flota generuje kolejne Stellarium. Klasyczny clicker.

Wybór akurat takiego gatunku nie był przypadkowy. Gra typu clicker to w praktyce aplikacja, która jest jednym wielkim stanem zmieniającym się w czasie. Nie ma tu miejsca, w którym możesz się schować za statycznym widokiem.

Co konkretnie musiałem rozgryźć, żeby to działało:

Stan, który rośnie sam z siebie. Waluta przyrasta w tle, niezależnie od tego, czy użytkownik klika. To oznacza interwał czasowy i pilnowanie, żeby licznik nie odwoływał się do wartości sprzed pięciu sekund. Klasyczna pułapka useEffect z niepełną listą zależności - i najlepsza możliwa lekcja o tym, czym w Reakcie jest domknięcie.

Wiele rzeczy zależnych od jednej liczby. Każda jednostka floty ma swój koszt, więc przy każdej zmianie salda trzeba przeliczyć, co jest już dostępne, a co jeszcze nie. Robiąc to naiwnie, przerysowujesz pół interfejsu przy każdym tyknięciu.

Zapis i wczytanie postępu. Gra idle bez zapisu nie ma sensu. Trzeba zdecydować, co trafia do localStorage, kiedy się zapisuje i jak wczytać stan tak, żeby nie nadpisać go pustymi wartościami z pierwszego renderu. To zresztą ten sam problem, który miałem w projekcie komercyjnym: znikające ustawienia po odświeżeniu.

Rosnąca struktura stanu. Przy trzech polach useState wystarcza. Przy kilkunastu jednostkach, ich poziomach, cenach i mnożnikach zaczyna się bałagan i naturalnie przechodzi się na useReducer z akcjami. Nagle okazało się, że frontend ma swoją wersję wzorca, który z backendu znam pod nazwą command handler.

Rozsądny podział na komponenty. Panel floty, licznik waluty, przycisk kliknięcia, pasek postępu. Każdy z nich musi dostać dokładnie tyle danych, ile potrzebuje, żeby nie renderować się bez powodu.

Największa różnica względem pracy z AI polegała na tym, że każdy błąd był mój. Nie mogłem wkleić komunikatu do czatu i dostać poprawki. Musiałem otworzyć narzędzia deweloperskie, sprawdzić, ile razy komponent się renderuje, i zrozumieć dlaczego. Trwało to dłużej. Zostało w głowie znacznie mocniej.

Efekt jest publiczny:

Co zmieniło się w mojej codziennej pracy z AI

Najciekawsze jest to, że nie przestałem używać AI. Zmienił się sposób, w jaki go używam.

Wcześniej pisałem prompty opisujące efekt: "zrób panel z listą jednostek i przyciskiem zakupu". Dostawałem kod, sprawdzałem czy działa w przeglądarce, jechałem dalej. Recenzja polegała na tym, że coś się wyświetlało.

Teraz piszę prompty opisujące rozwiązanie: gdzie ma siedzieć stan, co ma być komponentem prezentacyjnym, czy dane mają lecieć przez kontekst czy przez props. A potem czytam wygenerowany kod jak review od młodszego kolegi z zespołu i wyłapuję rzeczy, których wcześniej nie widziałem. Zbędny efekt, stan trzymany za nisko, klucz na liście ustawiony na indeks tablicy.

Zyskałem też coś, czego nie doceniałem: umiejętność powiedzenia "nie". Kiedy AI proponuje bibliotekę do zarządzania stanem w aplikacji, która ma pięć ekranów, wiem, że nie potrzebuję dodatkowej zależności i wystarczy mi kontekst z reducerem.

Praktyczny wniosek, który dotyczy każdego, kto dowozi kod z AI poza swoim głównym stackiem: AI skraca drogę do działającego rozwiązania, ale nie skraca drogi do kompetencji. To dwa różne cele i warto świadomie inwestować w oba, bo tylko drugi z nich zostaje z Tobą, kiedy zmieniasz projekt.

Następny krok: własne API w .NET z frontendem w React

Space Clicker nie ma backendu i to była świadoma decyzja. Chciałem odizolować frontend, żeby nie uciekać w komfortową strefę, kiedy coś nie wychodzi.

Teraz siedzę nad kolejnym projektem, w którym łączę oba światy: API w .NET i frontend w React, pisane ręcznie, bez generowania całych plików. Tam dochodzą tematy, które w grze w ogóle nie występowały - autoryzacja, obsługa błędów sieciowych po stronie interfejsu, stany ładowania, unieważnianie danych po zapisie i kontrakt między backendem a frontem, który nie rozjeżdża się po pierwszej zmianie modelu.

Jeżeli miałbym dać jedną radę backendowcowi, który dowozi front z AI i czuje ten sam dyskomfort co ja pół roku temu: nie zaczynaj od przepisywania projektu komercyjnego. Zrób sobie coś małego i bezużytecznego, ale takiego, gdzie mechanika wymusza zrozumienie fundamentu. Gra idle nadaje się do tego świetnie, bo pilnuje Cię w każdej sekundzie działania.

Co z tego wynika

Miesiące pracy z AI dały mi dowożone frontendy i fałszywe poczucie, że umiem React. Jeden kurs plus jeden projekt napisany własnymi rękami dały mi coś innego: możliwość oceny, czy kod, który wpuszczam do repozytorium, jest dobry.

Nadal używam AI do wszystkiego, co da się zautomatyzować. Ale teraz jestem tym, kto podejmuje decyzje, a nie tym, kto klika "zatwierdź".

Buduję prototypy i aplikacje webowe (PoC / MVP) dla firm, które chcą sprawdzić pomysł przed dużym projektem IT. Jeśli zastanawiasz się, czy Twoje narzędzie ma sens i jak mogłoby wyglądać w praktyce, opisałem sposób pracy na mwozniczka.net - napisz, pogadamy o Twoim przypadku.