Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

React Components - Budowanie UI

Teraz stworzymy komponenty React dla Kraken's Call. Nauczysz się tworzyć wielokrotnego użytku elementy UI.

Czym jest komponent?

Komponent to funkcja która zwraca JSX (wygląd UI). To jak klocki LEGO - budujesz małe kawałki i łączysz je w większą całość.

Prosty komponent:

1function WelcomeMessage() {
2  return (
3    <div className="bg-blue-100 p-4 rounded">
4      <h2 className="text-xl font-bold">Witaj na pokładzie!</h2>
5      <p>Gotowy do wysyłania wiadomości?</p>
6    </div>
7  );
8}

Używanie komponentu:

1export default function Home() {
2  return (
3    <div>
4      <WelcomeMessage />
5      <WelcomeMessage />
6      <WelcomeMessage />
7    </div>
8  );
9}

Props - Przekazywanie danych

Props to dane które przekazujesz do komponentu. Jak argumenty funkcji.

Komponent z props:

1function PirateCard({ name, role, avatar }: {
2  name: string;
3  role: string;
4  avatar: string;
5}) {
6  return (
7    <div className="bg-white p-6 rounded-lg shadow">
8      <div className="text-4xl mb-2">{avatar}</div>
9      <h3 className="text-xl font-bold">{name}</h3>
10      <p className="text-gray-600">{role}</p>
11    </div>
12  );
13}

Używanie z props:

1export default function Crew() {
2  return (
3    <div className="grid grid-cols-3 gap-4">
4      <PirateCard
5        name="Kapitan Redbeard"
6        role="Kapitan statku"
7        avatar="🏴‍☠️"
8      />
9      <PirateCard
10        name="Morgan Blackheart"
11        role="Nawigator"
12        avatar="🗺️"
13      />
14      <PirateCard
15        name="Polly Squawk"
16        role="Papuga komunikacyjna"
17        avatar="🦜"
18      />
19    </div>
20  );
21}

TypeScript Props - Typy dla bezpieczeństwa

Lepiej używać interface dla props:

1interface PirateCardProps {
2  name: string;
3  role: string;
4  avatar: string;
5  description?: string; // Opcjonalne pole
6}
7
8function PirateCard({ name, role, avatar, description }: PirateCardProps) {
9  return (
10    <div className="bg-white p-6 rounded-lg shadow">
11      <div className="text-4xl mb-2">{avatar}</div>
12      <h3 className="text-xl font-bold">{name}</h3>
13      <p className="text-gray-600">{role}</p>
14      {description && (
15        <p className="text-sm text-gray-500 mt-2">{description}</p>
16      )}
17    </div>
18  );
19}

Post Component - Twitter-style post

Stwórzmy komponent posta dla Kraken's Call:

1// components/Post.tsx
2interface PostProps {
3  author: string;
4  avatar: string;
5  content: string;
6  timestamp: string;
7  likes: number;
8}
9
10export default function Post({
11  author,
12  avatar,
13  content,
14  timestamp,
15  likes
16}: PostProps) {
17  return (
18    <div className="bg-white p-6 rounded-lg shadow hover:shadow-lg transition-shadow">
19      {/* Header */}
20      <div className="flex items-center gap-3 mb-4">
21        <div className="text-3xl">{avatar}</div>
22        <div>
23          <h3 className="font-bold text-gray-900">{author}</h3>
24          <p className="text-sm text-gray-500">{timestamp}</p>
25        </div>
26      </div>
27
28      {/* Content */}
29      <p className="text-gray-800 mb-4">{content}</p>
30
31      {/* Actions */}
32      <div className="flex items-center gap-6 text-gray-500">
33        <button className="flex items-center gap-2 hover:text-red-500">
34          ❤️ {likes}
35        </button>
36        <button className="flex items-center gap-2 hover:text-blue-500">
37          💬 Odpowiedz
38        </button>
39        <button className="flex items-center gap-2 hover:text-green-500">
40          🔄 Udostępnij
41        </button>
42      </div>
43    </div>
44  );
45}

Używanie komponentu Post:

1// app/page.tsx
2import Post from '@/components/Post';
3
4export default function Feed() {
5  return (
6    <div className="max-w-2xl mx-auto p-8 space-y-4">
7      <Post
8        author="Kapitan Redbeard"
9        avatar="🏴‍☠️"
10        content="Właśnie odkryliśmy nową wyspę! Mapa wskazuje na ogromny skarb 🗺️💰"
11        timestamp="2 godziny temu"
12        likes={42}
13      />
14      <Post
15        author="Morgan Blackheart"
16        avatar="⚓"
17        content="Kto chce dołączyć do wyprawy na północ? Szukamy odważnych marynarzy!"
18        timestamp="5 godzin temu"
19        likes={18}
20      />
21    </div>
22  );
23}

Organizacja komponentów

Standardowa struktura projektów Next.js:

1app/
2├── page.tsx              → Strony
3├── layout.tsx
4└── profile/
5    └── page.tsx
6
7components/                → Komponenty wielokrotnego użytku
8├── Post.tsx
9├── Navbar.tsx
10├── Button.tsx
11└── Avatar.tsx
12
13lib/                       → Utility functions
14└── utils.ts
15
16types/                     → TypeScript types
17└── index.ts

Client vs Server Components

W Next.js 14 domyślnie wszystko to Server Component.

Server Component (default):

  • Renderowany na serwerze
  • Nie ma dostępu do browser API (useState, useEffect)
  • Szybszy, lżejszy
  • Może fetchować dane bezpośrednio
1// Server Component (default)
2export default function ServerPost() {
3  // Może fetchować dane tutaj
4  return <div>Post</div>;
5}

Client Component:

  • Renderowany w przeglądarce
  • Może używać hooków (useState, useEffect)
  • Interaktywny (onClick, onChange)
  • Wymaga
    'use client'
    na górze
1'use client';
2
3import { useState } from 'react';
4
5export default function LikeButton() {
6  const [likes, setLikes] = useState(0);
7
8  return (
9    <button onClick={() => setLikes(likes + 1)}>
10      ❤️ {likes}
11    </button>
12  );
13}

Zasada: Używaj Server Components domyślnie. Dodaj

'use client'
tylko gdy potrzebujesz interaktywności.

Interaktywny Post z Like Button

1'use client';
2
3import { useState } from 'react';
4
5interface PostProps {
6  author: string;
7  avatar: string;
8  content: string;
9  initialLikes: number;
10}
11
12export default function InteractivePost({
13  author,
14  avatar,
15  content,
16  initialLikes
17}: PostProps) {
18  const [likes, setLikes] = useState(initialLikes);
19  const [isLiked, setIsLiked] = useState(false);
20
21  const handleLike = () => {
22    if (isLiked) {
23      setLikes(likes - 1);
24    } else {
25      setLikes(likes + 1);
26    }
27    setIsLiked(!isLiked);
28  };
29
30  return (
31    <div className="bg-white p-6 rounded-lg shadow">
32      <div className="flex items-center gap-3 mb-4">
33        <div className="text-3xl">{avatar}</div>
34        <h3 className="font-bold">{author}</h3>
35      </div>
36
37      <p className="mb-4">{content}</p>
38
39      <button
40        onClick={handleLike}
41        className={`flex items-center gap-2 ${
42          isLiked ? 'text-red-500' : 'text-gray-500'
43        }`}
44      >
45        {isLiked ? '❤️' : '🤍'} {likes}
46      </button>
47    </div>
48  );
49}

Children Prop - Komponenty wrapper

children
to specjalny prop - zawartość między tagami komponentu.

1interface CardProps {
2  children: React.ReactNode;
3}
4
5function Card({ children }: CardProps) {
6  return (
7    <div className="bg-white p-6 rounded-lg shadow">
8      {children}
9    </div>
10  );
11}
12
13// Użycie:
14<Card>
15  <h2>Tytuł</h2>
16  <p>Treść karty</p>
17</Card>

Conditional Rendering - Warunkowe wyświetlanie

Pokazywanie elementów warunkowo:

1function Post({ isVerified, isPremium }: {
2  isVerified: boolean;
3  isPremium: boolean;
4}) {
5  return (
6    <div>
7      <h3>Kapitan Redbeard</h3>
8
9      {/* If statement */}
10      {isVerified && <span>Zweryfikowany</span>}
11
12      {/* Ternary operator */}
13      {isPremium ? (
14        <span className="text-gold">Premium</span>
15      ) : (
16        <span className="text-gray-500">Darmowy</span>
17      )}
18    </div>
19  );
20}

Mapping Lists - Renderowanie list

Renderowanie tablicy danych:

1const pirates = [
2  { id: 1, name: 'Redbeard', role: 'Kapitan' },
3  { id: 2, name: 'Blackheart', role: 'Nawigator' },
4  { id: 3, name: 'Polly', role: 'Papuga' },
5];
6
7function PirateList() {
8  return (
9    <div className="space-y-4">
10      {pirates.map((pirate) => (
11        <div key={pirate.id} className="bg-white p-4 rounded">
12          <h3 className="font-bold">{pirate.name}</h3>
13          <p className="text-gray-600">{pirate.role}</p>
14        </div>
15      ))}
16    </div>
17  );
18}

Ważne: Zawsze dodawaj

key
prop przy mapowaniu!

Podsumowanie 🎓

Komponenty - funkcje zwracające JSX ✅ Props - przekazywanie danych do komponentów ✅ TypeScript interface - typy dla props ✅ Server Components - domyślne, szybkie ✅ Client Components -

'use client'
, interaktywne ✅ useState - stan komponentu ✅ children - zawartość między tagami ✅ Conditional rendering - && i ternary operator ✅ map() - renderowanie list z key prop

W następnym ćwiczeniu zbudujemy kompletny feed z możliwością dodawania postów!

Do zobaczenia! 🚀

Przejdź do CodeWorlds