Skip to main content

JSX and Components

What is JSX?

JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code in your JavaScript files. It's not HTML, but a syntactic sugar that gets compiled to regular JavaScript.

JSX Compilation

// JSX
const element = <h1 className='greeting'>Hello, World!</h1>;

// Compiles to (React 17+)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', {
className: 'greeting',
children: 'Hello, World!',
});

// Before React 17
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, World!'
);

JSX Rules

  1. Single Root Element: Components must return a single root element
// ❌ Wrong
function App() {
return (
<h1>Title</h1>
<p>Paragraph</p>
);
}

// ✅ Correct - Using Fragment
function App() {
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
}
  1. className instead of class
<div className='container'>Content</div>
  1. camelCase for attributes
<button onClick={handleClick} onMouseEnter={handleHover}>
Click me
</button>
  1. Self-closing tags must have /
<img src="image.jpg" alt="Description" />
<input type="text" />

Functional Components

Modern React uses functional components as the primary way to create components.

Basic Component

function Welcome() {
return <h1>Hello, React!</h1>;
}

// Arrow function syntax
const Welcome = () => {
return <h1>Hello, React!</h1>;
};

// Implicit return (single expression)
const Welcome = () => <h1>Hello, React!</h1>;

Component with Props

function Welcome({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old</p>
</div>
);
}

// Usage
<Welcome name='Alice' age={25} />;

TypeScript Components

interface WelcomeProps {
name: string;
age: number;
isActive?: boolean; // Optional prop
}

const Welcome = ({ name, age, isActive = true }: WelcomeProps) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
{isActive && <span>Active User</span>}
</div>
);
};

// Or using React.FC (less common in modern React)
const Welcome: React.FC<WelcomeProps> = ({ name, age }) => {
return <h1>Hello, {name}!</h1>;
};

JavaScript Expressions in JSX

You can embed any JavaScript expression in JSX using curly braces {}.

const name = 'Alice';
const age = 25;

function Greeting() {
const formatName = name => name.toUpperCase();

return (
<div>
{/* Variables */}
<h1>Hello, {name}!</h1>

{/* Expressions */}
<p>Next year you'll be {age + 1}</p>

{/* Function calls */}
<p>Uppercase: {formatName(name)}</p>

{/* Ternary operators */}
<p>{age >= 18 ? 'Adult' : 'Minor'}</p>

{/* Template literals */}
<p>{`User ${name} is ${age} years old`}</p>
</div>
);
}

Component Composition

Breaking down UI into smaller, reusable components.

// Small, focused components
const Avatar = ({ src, alt, size = 50 }) => (
<img
src={src}
alt={alt}
style={{ width: size, height: size, borderRadius: '50%' }}
/>
);

const UserInfo = ({ name, email }) => (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);

// Composed component
const UserCard = ({ user }) => (
<div className='user-card'>
<Avatar src={user.avatar} alt={user.name} />
<UserInfo name={user.name} email={user.email} />
</div>
);

// Usage
const user = {
name: 'Alice Johnson',
email: 'alice@example.com',
avatar: 'https://example.com/avatar.jpg',
};

<UserCard user={user} />;

Children Prop

Components can receive children elements.

const Card = ({ children, title }) => (
<div className='card'>
{title && <h2>{title}</h2>}
<div className='card-content'>{children}</div>
</div>
);

// Usage
<Card title='User Profile'>
<p>This is the card content</p>
<button>Click me</button>
</Card>;

TypeScript with Children

interface CardProps {
title?: string;
children: React.ReactNode;
}

const Card = ({ title, children }: CardProps) => (
<div className='card'>
{title && <h2>{title}</h2>}
{children}
</div>
);

Modern Component Patterns

Destructuring Props with Defaults

const Button = ({
text = 'Click me',
variant = 'primary',
disabled = false,
onClick,
}) => (
<button
className={`btn btn-${variant}`}
disabled={disabled}
onClick={onClick}
>
{text}
</button>
);

Spread Props

const Input = ({ label, ...inputProps }) => (
<div className='form-group'>
<label>{label}</label>
<input {...inputProps} />
</div>
);

// Usage
<Input label='Email' type='email' placeholder='Enter email' required />;

Conditional Rendering Patterns

const UserStatus = ({ user }) => {
// Early return pattern
if (!user) {
return <p>Loading...</p>;
}

return (
<div>
{/* Logical AND */}
{user.isPremium && <span className='badge'>Premium</span>}

{/* Ternary operator */}
{user.isOnline ? (
<span className='status online'>Online</span>
) : (
<span className='status offline'>Offline</span>
)}

{/* Nullish coalescing */}
<p>Points: {user.points ?? 0}</p>
</div>
);
};

Component File Organization

Modern Project Structure

src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.module.css
│ │ └── index.ts
│ ├── Card/
│ │ ├── Card.tsx
│ │ ├── Card.module.css
│ │ └── index.ts
│ └── index.ts // Barrel export
├── App.tsx
└── main.tsx

Barrel Exports

// components/index.ts
export { Button } from './Button';
export { Card } from './Card';
export { UserCard } from './UserCard';

// Usage in other files
import { Button, Card, UserCard } from '@/components';

Best Practices

  1. One Component Per File - Keep components focused and maintainable
  2. Use Descriptive Names - UserProfileCard instead of Card1
  3. Keep Components Small - Single Responsibility Principle
  4. Use TypeScript - Better type safety and developer experience
  5. Destructure Props - Cleaner and more readable
  6. Avoid Inline Functions in JSX - Can cause unnecessary re-renders
  7. Use Fragments - Avoid unnecessary wrapper divs

Common Pitfalls

1. Mutating Props

// ❌ Never mutate props
function Component({ user }) {
user.name = 'New Name'; // Don't do this!
return <div>{user.name}</div>;
}

// ✅ Props are read-only
function Component({ user }) {
return <div>{user.name}</div>;
}

2. Missing Keys in Lists

// ❌ Missing key
users.map(user => <UserCard user={user} />);

// ✅ With key
users.map(user => <UserCard key={user.id} user={user} />);

3. Incorrect Event Handlers

// ❌ Immediately invoked
<button onClick={handleClick()}>Click</button>

// ✅ Pass function reference
<button onClick={handleClick}>Click</button>

// ✅ With parameters
<button onClick={() => handleClick(id)}>Click</button>

Practice Exercise

Create a ProductCard component that:

  • Displays product image, name, price, and rating
  • Shows a "Sale" badge if on sale
  • Has an "Add to Cart" button
  • Uses TypeScript for type safety
  • Follows all best practices
interface Product {
id: string;
name: string;
price: number;
image: string;
rating: number;
onSale?: boolean;
}

interface ProductCardProps {
product: Product;
onAddToCart: (id: string) => void;
}

// Implement the component here

Next Steps