Teraz stworzymy komponenty React dla Kraken's Call. Nauczysz się tworzyć wielokrotnego użytku elementy UI.
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ść.
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}1export default function Home() {
2 return (
3 <div>
4 <WelcomeMessage />
5 <WelcomeMessage />
6 <WelcomeMessage />
7 </div>
8 );
9}Props to dane które przekazujesz do komponentu. Jak argumenty funkcji.
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}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}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}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}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}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.tsW Next.js 14 domyślnie wszystko to Server Component.
1// Server Component (default)
2export default function ServerPost() {
3 // Może fetchować dane tutaj
4 return <div>Post</div>;
5}'use client' na górze1'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.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 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>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}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!✅ 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 propW następnym ćwiczeniu zbudujemy kompletny feed z możliwością dodawania postów!
Do zobaczenia! 🚀