Now we'll create React components for Kraken's Call. You'll learn to create reusable UI elements.
A component is a function that returns JSX (UI appearance). It's like LEGO blocks - you build small pieces and combine them into a bigger whole.
1function WelcomeMessage() {
2 return (
3 <div className="bg-blue-100 p-4 rounded">
4 <h2 className="text-xl font-bold">Welcome aboard!</h2>
5 <p>Ready to send messages?</p>
6 </div>
7 );
8}1export default function Home() {
2 return (
3 <div>
4 <WelcomeMessage />
5 <WelcomeMessage />
6 <WelcomeMessage />
7 </div>
8 );
9}Props are data you pass to a component. Like function arguments.
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="Captain Redbeard"
6 role="Ship captain"
7 avatar="🏴☠️"
8 />
9 <PirateCard
10 name="Morgan Blackheart"
11 role="Navigator"
12 avatar="🗺️"
13 />
14 <PirateCard
15 name="Polly Squawk"
16 role="Communication parrot"
17 avatar="🦜"
18 />
19 </div>
20 );
21}It's better to use an interface for props:
1interface PirateCardProps {
2 name: string;
3 role: string;
4 avatar: string;
5 description?: string; // Optional field
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}Let's create a post component for 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 💬 Reply
38 </button>
39 <button className="flex items-center gap-2 hover:text-green-500">
40 🔄 Share
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="Captain Redbeard"
9 avatar="🏴☠️"
10 content="We just discovered a new island! The map points to a huge treasure 🗺️💰"
11 timestamp="2 hours ago"
12 likes={42}
13 />
14 <Post
15 author="Morgan Blackheart"
16 avatar="⚓"
17 content="Who wants to join the expedition to the north? Looking for brave sailors!"
18 timestamp="5 hours ago"
19 likes={18}
20 />
21 </div>
22 );
23}Standard Next.js project structure:
1app/
2├── page.tsx → Pages
3├── layout.tsx
4└── profile/
5 └── page.tsx
6
7components/ → Reusable components
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.tsIn Next.js 14 everything is a Server Component by default.
1// Server Component (default)
2export default function ServerPost() {
3 // Can fetch data here
4 return <div>Post</div>;
5}'use client' at the top1'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}Rule: Use Server Components by default. Add
'use client' only when you need interactivity.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 is a special prop - the content between component tags.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// Usage:
14<Card>
15 <h2>Title</h2>
16 <p>Card content</p>
17</Card>Showing elements conditionally:
1function Post({ isVerified, isPremium }: {
2 isVerified: boolean;
3 isPremium: boolean;
4}) {
5 return (
6 <div>
7 <h3>Captain Redbeard</h3>
8
9 {/* If statement */}
10 {isVerified && <span>✅ Verified</span>}
11
12 {/* Ternary operator */}
13 {isPremium ? (
14 <span className="text-gold">⭐ Premium</span>
15 ) : (
16 <span className="text-gray-500">Free</span>
17 )}
18 </div>
19 );
20}Rendering an array of data:
1const pirates = [
2 { id: 1, name: 'Redbeard', role: 'Captain' },
3 { id: 2, name: 'Blackheart', role: 'Navigator' },
4 { id: 3, name: 'Polly', role: 'Parrot' },
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}Important: Always add the
key prop when mapping!✅ Components - functions returning JSX ✅ Props - passing data to components ✅ TypeScript interface - types for props ✅ Server Components - default, fast ✅ Client Components -
'use client', interactive
✅ useState - component state
✅ children - content between tags
✅ Conditional rendering - && and ternary operator
✅ map() - rendering lists with key propIn the next exercise we'll build a complete feed with the ability to add posts!
See you there! 🚀