JSX Fundamentals

JSX stands for JavaScript XML. It's a syntax extension for JavaScript that allows you to write HTML-like code within your JavaScript files. JSX makes it more intuitive to create and visualize the structure of your UI components in React.

What is JSX?

JSX combines the power of JavaScript with the familiarity of HTML. It allows you to:

  • Write markup directly in your JavaScript code
  • Use JavaScript expressions within your markup
  • Create complex UIs with component-based architecture

Here's a simple example of JSX:

const Element = () =>  {
	return (
		<h1>Hello, World!</h1>;
	)
}

JSX vs JavaScript: The Difference

To truly understand the value of JSX, let's compare how we would create UI elements with and without it.

Without JSX (Pure JavaScript)

// Without JSX, we'd use createElementS directly
import { createElement } from "react";

const element = createElement(
  "h1",
  { className: "greeting" },
  "Hello, World!"
);

// Nested elements become verbose and hard to read
const container = createElement(
  "div",
  { className: "container" },
  createElement("h1", null, "Title"),
  createElement(
    "p",
    { className: "content" },
    "Some paragraph text",
    createElement("a", { href: "/link" }, "Click here")
  )
);

With JSX (in TSX file)

// The same elements, but with JSX
const Element = () => {
  return <h1 className="greeting">Hello, World!</h1>;
};

// Nested elements are much more readable
const Container = () => {
  return (
    <div className="container">
      <h1>Title</h1>
      <p className="content">
        Some paragraph text
        <a href="/link">Click here</a>
      </p>
    </div>
  );
};

As you can see, JSX makes the code much more readable and intuitive, especially for nested elements. Behind the scenes, JSX is transformed into createElement() calls during the build process.

TS vs TSX Files: When to Use Each

TypeScript files come in two main flavors:

  • .ts files: Standard TypeScript files
  • .tsx files: TypeScript files that include JSX syntax

When to Use .ts Files

Use .ts files for:

  • Pure logic or utility functions
  • Services and API calls
  • State management (stores, reducers)
  • Type definitions and interfaces
  • Non-UI related code

Example of a .ts file:

// userService.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

export function fetchUser(id: string): Promise<User> {
  return fetch(`/api/users/${id}`).then((response) => response.json());
}

When to Use .tsx Files

Use .tsx files for:

  • React components
  • Any file that contains JSX syntax
  • Custom hooks that return JSX
  • Higher-order components

Example of a .tsx file:

// user-profile.tsx
import { useState, useEffect } from "react";
import { User, fetchUser } from "./user-service";

interface UserProfileProps {
  userId: string;
}

export const UserProfile = ({ userId }: UserProfileProps) => {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);

  useEffect(() => {
    fetchUser(userId).then((userData) => {
      setUser(userData);
      setIsLoading(false);
    });
  }, [userId]);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div className="user-profile">
      <h2>{user?.name}</h2>
      <p>{user?.email}</p>
    </div>
  );
};

Important: Any file that contains JSX syntax must have the .tsx extension, or TypeScript will report errors.

JSX vs HTML: Key Differences

While JSX looks like HTML, there are important differences:

  1. Attributes use camelCase: HTML uses kebab-case (like onclick), JSX uses camelCase (like onClick)

    // HTML
    <button onclick="handleClick()">Click me</button>
    
    // JSX
    <button onClick={handleClick}>Click me</button>
    
  2. className instead of class: Since class is a reserved keyword in JavaScript

    // HTML
    <div class="container">Content</div>
    
    // JSX
    <div className="container">Content</div>
    
  3. Self-closing tags: All tags must be closed, including self-closing tags like <img />

    // HTML (may work without closing slash)
    <img src="image.jpg" alt="An image">
    
    // JSX (requires closing slash)
    <img src="image.jpg" alt="An image" />
    
  4. JavaScript expressions: You can embed JavaScript expressions using curly braces {}

    // JSX with expressions
    const name: string = "User";
    <h1>Hello, {name}!</h1>
    <button onClick={() => console.log('Clicked')}>Click</button>
    
  5. Style attribute takes an object: Inline styles use objects with camelCase properties

    // HTML
    <div style="background-color: blue; font-size: 16px;">Text</div>
    
    // JSX
    <div style={{ backgroundColor: 'blue', fontSize: '16px' }}>Text</div>
    

JSX with TypeScript

TypeScript enhances JSX by adding static type checking. This helps catch errors at compile time rather than at runtime.

Type Definitions for JSX Elements

React with TypeScript provides several types for JSX elements:

// Basic JSX element
const heading: JSX.Element = <h1>Welcome</h1>;

// For a function component
const Greeting = (): JSX.Element => {
  return <p>Hello there!</p>;
};

Type-Safe Props with Interfaces

interface UserCardProps {
  name: string;
  email: string;
  age: number;
  isAdmin?: boolean; // Optional prop with '?'
}

// Function component with typed props
const UserCard = (props: UserCardProps) => {
  return (
    <div className="user-card">
      <h2>{props.name}</h2>
      <p>Email: {props.email}</p>
      <p>Age: {props.age}</p>
      {props.isAdmin && <span className="badge">Admin</span>}
    </div>
  );
};

// Destructured props version
const UserCardDestructured = ({ name, email, age, isAdmin = false }: UserCardProps) => {
  return (
    <div className="user-card">
      <h2>{name}</h2>
      <p>Email: {email}</p>
      <p>Age: {age}</p>
      {isAdmin && <span className="badge">Admin</span>}
    </div>
  );
};

Children Props in TypeScript

There are several ways to type components that accept children:

// Using the React.ReactNode type for flexible children
import { ReactNode } from "react";

interface ContainerProps {
  children: ReactNode;
  className?: string;
}

const Container = ({ children, className = "" }: ContainerProps) => {
  return <div className={`container ${className}`}>{children}</div>;
};

// Usage
const App = () => {
  return (
    <Container className="main-content">
      <h1>Title</h1>
      <p>Content</p>
    </Container>
  );
};

JavaScript Expressions in JSX

You can embed any JavaScript expression inside JSX using curly braces {}. This is one of the most powerful features of JSX:

// Variable interpolation
const name: string = "TypeScript";
const element = <h1>Hello, {name}!</h1>;

// Function calls
function formatName(user: { firstName: string; lastName: string }): string {
  return `${user.firstName} ${user.lastName}`;
}

const user = { firstName: "John", lastName: "Doe" };
const greeting = <h1>Hello, {formatName(user)}!</h1>;

// Object properties
const imageUrl: string = "/images/logo.png";
const image = <img src={imageUrl} alt="Logo" />;

// Expressions in attributes
const buttonId: string = "submit-button";
const isActive = true;
const button = (
  <button id={buttonId} className={`btn ${isActive ? "active" : ""}`}>
    Submit
  </button>
);

// Expressions for inline styles
const fontSize: number = 16;
const styles: React.CSSProperties = {
  color: "blue",
  fontSize: `${fontSize}px`,
  fontWeight: "bold",
};
const styledDiv = <div style={styles}>Styled text</div>;

Conditional Rendering in JSX

There are several patterns for conditionally rendering elements:

Using Ternary Operators

interface UserProps {
  isLoggedIn: boolean;
  username: string;
}

const Greeting = (props: UserProps) => {
  return (
    <div>
      {props.isLoggedIn ? <h1>Welcome back, {props.username}!</h1> : <h1>Please sign in</h1>}
    </div>
  );
};

Using Logical && Operator

This pattern works when you want to render something or nothing:

interface NotificationProps {
  hasNotifications: boolean;
  count: number;
}

const NotificationBadge = (props: NotificationProps) => {
  return (
    <div className="notification-container">
      {props.hasNotifications && <span className="badge">{props.count}</span>}
      <i className="icon-bell"></i>
    </div>
  );
};

Using Variables and if Statements

For more complex conditions, you can use variables:

interface UserStatusProps {
  status: "online" | "away" | "offline" | "busy";
}

const UserStatus = (props: UserStatusProps) => {
  let statusElement;

  if (props.status === "online") {
    statusElement = <span className="status-online">Online</span>;
  } else if (props.status === "away") {
    statusElement = <span className="status-away">Away</span>;
  } else if (props.status === "busy") {
    statusElement = <span className="status-busy">Do Not Disturb</span>;
  } else {
    statusElement = <span className="status-offline">Offline</span>;
  }

  return <div className="user-status">{statusElement}</div>;
};

JSX Lists and Keys

When rendering lists in React, each item should have a unique "key" prop:

interface TodoItem {
  id: number;
  text: string;
  completed: boolean;
}

const TodoList = () => {
  const todos: TodoItem[] = [
    { id: 1, text: "Learn React", completed: false },
    { id: 2, text: "Learn TypeScript", completed: false },
    { id: 3, text: "Build something awesome", completed: false },
  ];

  return (
    <ul className="todo-list">
      {todos.map((todo) => (
        <li key={todo.id} className={todo.completed ? "completed" : ""}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
};

Why Keys Matter

Keys help React identify which items have changed, been added, or removed. Without keys, React would have to re-render the entire list whenever any item changes.

// Poor performance example (without unique keys)
const BadListExample = () => {
  const items: string[] = ["Apple", "Banana", "Cherry"];

  return (
    <ul>
      {items.map((item, index) => (
        // Using index as key is generally NOT recommended when the list order might change
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
};

// Good performance example (with unique keys)
interface ListItem {
  id: string;
  name: string;
}

const GoodListExample = () => {
  const items: ListItem[] = [
    { id: "fruit-1", name: "Apple" },
    { id: "fruit-2", name: "Banana" },
    { id: "fruit-3", name: "Cherry" },
  ];

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

JSX and Event Handling

Event handling in JSX uses camelCase and passes functions as event handlers:

interface ButtonProps {
  text: string;
  onClick?: () => void; // Optional click handler
}

const Button = (props: ButtonProps) => {
  // Default handler if none provided
  const handleClick = (): void => {
    console.log("Button was clicked!");
  };

  return <button onClick={props.onClick || handleClick}>{props.text}</button>;
};

// With event parameter
const InputExample = () => {
  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    console.log("Input value:", event.target.value);
  };

  return <input type="text" onChange={handleInputChange} placeholder="Type something" />;
};

// Handling multiple events
const Form = () => {
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
    event.preventDefault(); // Prevent default form submission
    console.log("Form submitted");
  };

  const handleReset = (): void => {
    console.log("Form reset");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="username" />
      <button type="submit">Submit</button>
      <button type="button" onClick={handleReset}>
        Reset
      </button>
    </form>
  );
};

Fragment Syntax

What Are Fragments?

Fragments are a special feature in React that allows you to group multiple elements together without adding an extra node to the DOM. In standard HTML, you can't return multiple adjacent elements without a parent container. React fragments solve this problem. Fragments address a fundamental limitation in JSX: a component must return a single root element. Before fragments, developers would often add unnecessary <div> elements as wrappers, which could cause styling issues or break semantic HTML structure.

How to Use Fragments

React provides two syntaxes for fragments:

  1. Long syntax (when you need to provide attributes like key):
import { Fragment } from "react";

const ListItems = () => {
  return (
    <Fragment key="fragment-1">
      <li>Item 1</li>
      <li>Item 2</li>
    </Fragment>
  );
};
  1. Short syntax (more common, but cannot include attributes):
const UserProfile = () => {
  return (
    <>
      <h1>User Profile</h1>
      <p>Name: John Doe</p>
      <p>Email: john@example.com</p>
    </>
  );
};

The <>...</> syntax is shorthand for <Fragment>...</Fragment>. Use the long syntax when you need to provide a key attribute.

Why Fragments Are Important

Fragments provide several benefits:

  1. Cleaner DOM: They don't add unnecessary nodes to the DOM tree
  2. Faster rendering: Fewer DOM nodes means slightly better performance
  3. Less CSS side effects: No extra wrapper elements to interfere with CSS selectors, flexbox, or grid layouts
  4. Semantic HTML: Allows you to maintain proper HTML structure (like table rows within tables)

Compare these two approaches:

// Without fragments (adds extra div to DOM)
const WithoutFragment = () => {
  return (
    <div> {/* Extra div that might break styling or semantics */}
      <h1>Title</h1>
      <p>Paragraph</p>
    </div>
  );
};

// With fragments (no extra DOM node)
const WithFragment = () => {
  return (
    <>
      <h1>Title</h1>
      <p>Paragraph</p>
    </>
  );
};

Common Use Cases for Fragments

1. Component that returns multiple elements:

const Header = () => {
  return (
    <>
      <h1>Website Title</h1>
      <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
    </>
  );
};

2. Returning multiple rows in a table:

const TableRows = ({ items }) => {
  return (
    <>
      {items.map(item => (
        <tr key={item.id}>
          <td>{item.name}</td>
          <td>{item.value}</td>
        </tr>
      ))}
    </>
  );
};

3. Conditionally rendering multiple elements:

const Notification = ({ isError, message }) => {
  return (
    <>
      {isError && (
        <>
          <div className="error-icon" />
          <p className="error-message">{message}</p>
        </>
      )}
    </>
  );
};

Fragments help maintain clean DOM structure and avoid styling issues that might be caused by extra container elements, making them an essential feature in modern React development.

JSX Spread Attributes

You can use the spread operator (...) to pass all properties from an object:

interface ButtonProps {
  type: "button" | "submit" | "reset";
  className: string;
  disabled?: boolean;
  onClick?: () => void;
}

const Button = (props: ButtonProps) => {
  const { className, ...otherProps } = props;

  return (
    <button className={`btn ${className}`} {...otherProps}>
      Click Me
    </button>
  );
};

// Usage
const App = () => {
  const buttonProps: ButtonProps = {
    type: "button",
    className: "primary",
    disabled: false,
    onClick: () => console.log("Button clicked"),
  };

  return <Button {...buttonProps} />;
};

Combining Spread Attributes with Specific Attributes

const App = () => {
  const buttonProps: ButtonProps = {
    type: "button",
    className: "secondary",
    onClick: () => console.log("Default click"),
  };

  // Specific attributes override spread attributes
  return (
    <Button
      {...buttonProps}
      className="primary" // This overrides className from buttonProps
      disabled={true} // This overrides disabled from buttonProps (if any)
    />
  );
};

JSX Comments

Comments in JSX must be wrapped in curly braces and use JavaScript's multi-line comment syntax:

const Element = () => {
	return (
		<div>
			{/* This is a comment inside JSX */}
			<p>This is visible text</p>
			{/*
				Multi-line
				comments
				work too
			*/}
			{false && "This won't be rendered"}
		</div>
	)
};

Type-Safe Children with TypeScript

TypeScript allows us to strictly type the children elements:

import { ReactNode } from "react";

// For a component that only accepts string children
interface TextBoxProps {
  children: string;
}

const TextBox = ({ children }: TextBoxProps) => {
  return <div className="text-box">{children}</div>;
};

// For a component that accepts specific component types
interface DialogProps {
  title: string;
  children: ReactNode;
}

const Dialog = ({ title, children }: DialogProps) => {
  return (
    <div className="dialog">
      <div className="dialog-header">
        <h2>{title}</h2>
      </div>
      <div className="dialog-content">{children}</div>
    </div>
  );
};

Common Pitfalls and Best Practices

Avoiding Direct HTML Injection

Never directly inject user input as HTML, as it can lead to XSS attacks:

// DON'T do this
const Comment = (props: { content: string }) => {
  return <div dangerouslySetInnerHTML={{ __html: props.content }} />;
};

// DO this instead
const Comment = (props: { content: string }): JSX.Element => {
  return <div>{props.content}</div>;
};

Case Sensitivity

JSX is case-sensitive. All built-in HTML elements start with lowercase, while custom React components should start with uppercase:

// HTML element - lowercase
const paragraph = <p>This is a paragraph</p>;

// Custom component - uppercase
const MyComponent = () => {
  return <div>My Component</div>;
};

const element = <MyComponent />;

String Attributes vs Expression Attributes

Be careful with the syntax for string literals versus expressions:

// String literals use quotes
<div id="main-content" className="container"></div>

// JavaScript expressions use curly braces
const dynamicId = "dynamic-content";
const activeClass = "active";
<div id={dynamicId} className={`container ${activeClass}`}></div>

// Common mistake: using quotes around expressions
<div id="{dynamicId}"></div> // WRONG - This will render the literal string "{dynamicId}"

Boolean Attributes

In JSX, boolean attributes can be specified without a value:

// These are equivalent
<button disabled={true}>Disabled Button</button>
<button disabled>Disabled Button</button>

// False values must be explicitly specified
<button disabled={false}>Enabled Button</button>

JSX Transformation Process

Understanding how JSX is transformed into JavaScript helps debug issues:

// Original JSX
const element = (
  <button className="btn" onClick={() => console.log("clicked")}>
    Click me
  </button>
);

// After transformation (simplified)
import { createElement } from "react";

const element = createElement(
  "button",
  {
    className: "btn",
    onClick: () => console.log("clicked"),
  },
  "Click me"
);

Exercises

Exercise 1: Convert HTML to JSX

Instructions

  1. Create a new file called simple-profile.tsx
  2. Convert the following HTML into proper JSX, fixing all syntax issues
  3. Add a TypeScript return type to your function
  4. Make sure to address all differences between HTML and JSX:
    • Change class attributes to className
    • Fix the img tag to be self-closing with />
    • Replace the onclick with a proper React onClick handler
    • Add an alt attribute to the image
<div class="profile">
  <img src="avatar.jpg" class="avatar" />
  <h2>Welcome, User!</h2>
  <button onclick="alert('Hello!')">Say Hello</button>
</div>

Key changes

  1. classclassName
  2. onclickonClick with a function reference
  3. Added closing slash to the img tag
  4. Added alt attribute for accessibility
  5. Replaced inline string function with a proper function reference

Exercise 2: JavaScript Expressions in JSX

Task: Create a simple JSX element that displays the current date and time. Use JavaScript expressions within JSX.

Instructions

  1. Create a new file called current-time.tsx
  2. Create a component that displays the current date and time
  3. Use the JavaScript Date object to get the current time
  4. Display the following information using JSX and JavaScript expressions:
    • The current date in a readable format
    • The current time in a readable format
    • A greeting that says "Good morning" or "Good afternoon" based on the current hour
  5. Make sure to add proper TypeScript types to all variables

Hint

Use the Date() constructor to create a new date object, and methods like toLocaleDateString() and toLocaleTimeString() to format the date and time.

Exercise 3: Simple List Rendering

Instructions

  1. Create a new file called language-list.tsx
  2. Create a component that displays a list of programming languages
  3. Inside your component:
    • Create an array of strings containing at least 5 programming languages
    • Use the array's map() function to convert each language name into a list item
    • Remember to add a unique key prop to each list item
  4. Make sure to add proper TypeScript types to your array

Hint: The syntax for mapping an array to JSX elements is:

{arrayName.map((item, index) => (
  <li key={index}>{item}</li>
))}

Conclusion

JSX is a powerful syntax extension that bridges HTML and JavaScript, making UI development more intuitive and maintainable. With TypeScript integration through .tsx files, you gain additional benefits of static type checking, which helps catch errors early and provides better developer tooling.

Key takeaways:

  1. JSX makes UI code more readable and intuitive than raw React.createElement() calls
  2. Use .tsx file extension for any file containing JSX syntax
  3. Use .ts file extension for non-UI related code and utilities
  4. TypeScript enhances JSX with static typing for props, events, and elements
  5. JSX looks like HTML but has important differences (camelCase, className, self-closing tags)
  6. Fragments help create cleaner component structures

As you continue your React journey, you'll find that JSX with TypeScript becomes a natural and efficient way to express your UI components with increased safety and developer experience.