React Fundamentals

React Fundamentals

Components, JSX, Props & State

CSCI 4513

Week 11, Lecture 19

Today's Learning Objectives

Norse Mythology Connection

The Creation of Ask and Embla

Three gods each gave distinct gifts to transform lifeless wood into living humans, just as state, interactivity, and JSX combine to transform static HTML into dynamic React applications.

The Tale:

In the early days after the gods had shaped land, sea, and sky, Odin, Vili, and Vé walked along a beach and discovered two tree trunks washed ashore—an ash tree (Ask) and an elm tree (Embla). These were lifeless pieces of wood, beautiful in form but inanimate and empty.

The three gods saw potential and decided to transform them into the first humans. Odin gave them önd—breath, spirit, and life force. Vili gave them óðr and lá—consciousness, the ability to move, and willpower. Vé gave them lá, lúr, and heill—appearance, senses, and facial features. None of these gifts alone would have been sufficient. Breath without consciousness is merely animation. Consciousness without form has no way to interact with the world. Form without life is just another tree trunk. But together, these three gifts transformed lifeless wood into living, thinking, perceiving humans who could experience and shape their world.

Bringing Components to Life

// Static HTML - lifeless tree trunk
<div class="user-card">
    <h2>User Name</h2>
    <p>Status: Active</p>
</div>

// Odin's gift - breath/life through state
function UserCard() {
    const [user, setUser] = useState({
        name: 'User Name',
        status: 'Active'
    });
    // Component can now "breathe" - data changes
}

// Vili's gift - consciousness and action
const handleClick = () => {
    setUser(prev => ({...prev, status: 'Inactive'}));
};

useEffect(() => {
    // Component can act autonomously
}, []);

// Vé's gift - appearance and perception
return (
    <div className="user-card">
        <h2>{user.name}</h2>
        <button onClick={handleClick}>Toggle</button>
    </div>
);

Reflection Questions

React Components

The Building Blocks of React Apps

What is a Component?

A component is a piece of UI (user interface) that has its own logic and appearance.

Components can be as small as a button, or as large as an entire page.

Key Concept: React applications are built by composing components together.

Creating a Functional Component

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

// Components are just JavaScript functions!
// They return JSX (which looks like HTML)

Requirements:

  • Must return JSX markup
  • Component names must be capitalized
  • Can accept props as parameters

Component Naming

⚠️ Component Names Must Be Capitalized!

React uses capitalization to distinguish between HTML tags and components.

// ❌ Wrong - lowercase (React thinks it's HTML)
function welcome() {
    return <h1>Hello!</h1>;
}

// ✅ Correct - capitalized
function Welcome() {
    return <h1>Hello!</h1>;
}

Exporting Components

// Default export (one per file)
export default function Welcome() {
    return <h1>Hello!</h1>;
}

// Named export (multiple per file)
export function Welcome() {
    return <h1>Hello!</h1>;
}

export function Goodbye() {
    return <h1>See you later!</h1>;
}

Importing Components

// Importing a default export
import Welcome from './Welcome';

// Importing named exports
import { Welcome, Goodbye } from './Greetings';

// Importing both
import Welcome, { Goodbye } from './Greetings';

// Using the imported component
function App() {
    return (
        <div>
            <Welcome />
            <Goodbye />
        </div>
    );
}

What is JSX?

JavaScript XML

JSX is a syntax extension for JavaScript

It lets you write HTML-like markup inside JavaScript files

// JSX looks like HTML...
const element = <h1>Hello, world!</h1>;

// But it's actually JavaScript!
// Babel converts it to:
const element = React.createElement(
    'h1',
    null,
    'Hello, world!'
);

The Three Rules of JSX

1. Return a single root element

Wrap multiple elements in a parent (or use fragments)

2. Close all tags

Self-closing tags need the slash: <img />, <br />

3. camelCase most things

class → className, onclick → onClick

Rule 1: Single Root Element

// ❌ Wrong - multiple root elements
function App() {
    return (
        <h1>Hello</h1>
        <p>Welcome!</p>
    );
}

// ✅ Correct - wrapped in parent
function App() {
    return (
        <div>
            <h1>Hello</h1>
            <p>Welcome!</p>
        </div>
    );
}

// ✅ Also correct - using Fragment
function App() {
    return (
        <>
            <h1>Hello</h1>
            <p>Welcome!</p>
        </>
    );
}

Rule 2: Close All Tags

// ❌ Wrong - unclosed tags
<img src="photo.jpg">
<br>
<input type="text">

// ✅ Correct - all tags closed
<img src="photo.jpg" />
<br />
<input type="text" />

// Regular tags also need closing
<div>
    <p>Content</p>
</div>

Rule 3: camelCase Attributes

// HTML attribute → JSX attribute
class          → className
for            → htmlFor
onclick        → onClick
onchange       → onChange
tabindex       → tabIndex
stroke-width   → strokeWidth

// Example:
<div class="container">          {/* HTML */}
<div className="container">      {/* JSX */}

<label for="name">              {/* HTML */}
<label htmlFor="name">          {/* JSX */}

Embedding JavaScript in JSX

// Use curly braces {} to embed JavaScript
function Greeting() {
    const name = "Alice";
    const time = new Date().toLocaleTimeString();

    return (
        <div>
            <h1>Hello, {name}!</h1>
            <p>The time is {time}</p>
            <p>2 + 2 = {2 + 2}</p>
        </div>
    );
}

// Any valid JavaScript expression works!
// Variables, function calls, math, etc.

Converting HTML to JSX

// HTML
<div class="card">
    <img src="avatar.jpg">
    <label for="name">Name:</label>
    <input type="text" id="name">
</div>

// JSX
<div className="card">
    <img src="avatar.jpg" />
    <label htmlFor="name">Name:</label>
    <input type="text" id="name" />
</div>

Tip: Use an HTML to JSX converter for large blocks!

Search for "HTML to JSX converter" online

Rendering Lists

Using .map() to Render Arrays

function FruitList() {
    const fruits = ['Apple', 'Banana', 'Cherry'];

    return (
        <ul>
            {fruits.map(fruit => (
                <li>{fruit}</li>
            ))}
        </ul>
    );
}

// Output:
// • Apple
// • Banana
// • Cherry

Rendering Complex Lists

function UserList() {
    const users = [
        { id: 1, name: 'Alice', age: 25 },
        { id: 2, name: 'Bob', age: 30 },
        { id: 3, name: 'Charlie', age: 35 }
    ];

    return (
        <div>
            {users.map(user => (
                <div key={user.id}>
                    <h2>{user.name}</h2>
                    <p>Age: {user.age}</p>
                </div>
            ))}
        </div>
    );
}

Keys in React

Why Does React Need Keys?

Keys help React identify which items have changed, been added, or removed

Without keys, React can't track list items efficiently

⚠️ Warning

If you don't provide keys, React will use array indices by default.

This can cause bugs when the list order changes!

Using Keys Properly

// ✅ Good - using unique IDs
const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
];

users.map(user => (
    <div key={user.id}>{user.name}</div>
));

// ❌ Bad - using array index (can cause bugs)
users.map((user, index) => (
    <div key={index}>{user.name}</div>
));

// ❌ Worse - generating keys during render
users.map(user => (
    <div key={Math.random()}>{user.name}</div>
));

When Can You Use Array Index?

✅ Safe to use index as key when:

  • The list is static (never changes)
  • Items are never reordered
  • Items are never filtered
  • Items don't have unique IDs

⚠️ Don't use index when:

  • Items can be added/removed
  • List can be sorted or filtered
  • Items have unique identifiers

Conditional Rendering

Show Different UI Based on Conditions

// Using if/else
function Greeting({ isLoggedIn }) {
    if (isLoggedIn) {
        return <h1>Welcome back!</h1>;
    } else {
        return <h1>Please sign in.</h1>;
    }
}

// Using ternary operator
function Greeting({ isLoggedIn }) {
    return (
        <h1>
            {isLoggedIn ? 'Welcome back!' : 'Please sign in.'}
        </h1>
    );
}

Conditional Rendering with &&

// Show element only if condition is true
function Notifications({ count }) {
    return (
        <div>
            <h1>Inbox</h1>
            {count > 0 && (
                <p>You have {count} unread messages</p>
            )}
        </div>
    );
}

// If count is 0, nothing renders
// If count > 0, the paragraph renders

Tip: Use && when you only want to show something if the condition is true

Props

Passing Data to Components

Props (short for "properties") are how you pass data from parent to child components

Think of props like function parameters - they let you customize a component

// Parent passes props
<Greeting name="Alice" age={25} />

// Child receives props
function Greeting(props) {
    return <h1>Hello, {props.name}! Age: {props.age}</h1>;
}

Destructuring Props

// Without destructuring
function Greeting(props) {
    return <h1>Hello, {props.name}!</h1>;
}

// With destructuring (preferred!)
function Greeting({ name, age }) {
    return (
        <div>
            <h1>Hello, {name}!</h1>
            <p>Age: {age}</p>
        </div>
    );
}

// Usage is the same
<Greeting name="Alice" age={25} />

Default Props

// Using default parameters
function Greeting({ name = "Guest", role = "User" }) {
    return (
        <div>
            <h1>Hello, {name}!</h1>
            <p>Role: {role}</p>
        </div>
    );
}

// If no props provided, defaults are used
<Greeting />                    // Hello, Guest! Role: User
<Greeting name="Alice" />       // Hello, Alice! Role: User
<Greeting name="Bob" role="Admin" />  // Hello, Bob! Role: Admin

Passing Functions as Props

// Parent component
function App() {
    const handleClick = (name) => {
        alert(`Hello, ${name}!`);
    };

    return <Button onClick={handleClick} text="Click me" />;
}

// Child component
function Button({ onClick, text }) {
    return (
        <button onClick={() => onClick(text)}>
            {text}
        </button>
    );
}

This is how child components communicate with parents!

State

Component Memory

State is data that changes over time

When state changes, React re-renders the component to reflect the new data

Key Difference:

Props are passed from parent (like function parameters)

State is managed within the component (like local variables)

The useState Hook

import { useState } from 'react';

function Counter() {
    // useState returns an array with 2 elements:
    // 1. Current state value
    // 2. Function to update the state
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}

How useState Works

const [count, setCount] = useState(0);
//     ^       ^                    ^
//     |       |                    |
//   state   setter           initial value

// Reading state
console.log(count);  // 0

// Updating state
setCount(5);         // count is now 5
setCount(count + 1); // count is now 6

⚠️ Important

State updates trigger a re-render of the component

Never modify state directly! Always use the setter function

State as a Snapshot

function Counter() {
    const [count, setCount] = useState(0);

    const handleClick = () => {
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        // What is count now? 3? No! It's 1!
    };

    return <button onClick={handleClick}>{count}</button>;
}

Why? State is a snapshot at render time

All three calls use the same count value (0)

So they all set count to 1

State Updater Functions

function Counter() {
    const [count, setCount] = useState(0);

    const handleClick = () => {
        // ❌ Wrong - uses snapshot value
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        // Result: count = 1

        // ✅ Correct - uses updater function
        setCount(prev => prev + 1);
        setCount(prev => prev + 1);
        setCount(prev => prev + 1);
        // Result: count = 3
    };

    return <button onClick={handleClick}>{count}</button>;
}

State Immutability

⚠️ Never Mutate State Directly!

const [user, setUser] = useState({ name: 'Alice', age: 25 });

// ❌ Wrong - mutating state directly
user.age = 26;           // Don't do this!
setUser(user);           // React won't detect the change

// ✅ Correct - create new object
setUser({ ...user, age: 26 });

// For arrays
const [items, setItems] = useState([1, 2, 3]);

// ❌ Wrong
items.push(4);

// ✅ Correct
setItems([...items, 4]);

State Structure Best Practices

✅ DO:

  • Group related state together
  • Avoid redundant state (derive it instead)
  • Avoid deeply nested state
  • Keep state as simple as possible

❌ DON'T:

  • Store the same data in multiple states
  • Duplicate data from props in state
  • Have contradictory state variables

Controlled Components

Syncing State with Form Inputs

function Form() {
    const [name, setName] = useState('');

    return (
        <div>
            <input
                type="text"
                value={name}
                onChange={(e) => setName(e.target.value)}
            />
            <p>Hello, {name}!</p>
        </div>
    );
}

// The input is "controlled" because:
// 1. Its value comes from state
// 2. onChange updates the state
// React controls what's displayed

Multiple Inputs

function RegistrationForm() {
    const [formData, setFormData] = useState({
        name: '',
        email: '',
        age: ''
    });

    const handleChange = (e) => {
        setFormData({
            ...formData,
            [e.target.name]: e.target.value
        });
    };

    return (
        <form>
            <input name="name" value={formData.name}
                   onChange={handleChange} />
            <input name="email" value={formData.email}
                   onChange={handleChange} />
            <input name="age" value={formData.age}
                   onChange={handleChange} />
        </form>
    );
}

Rules of Hooks

⚠️ Two Critical Rules:

1. Only call hooks at the top level

Don't call hooks inside loops, conditions, or nested functions

2. Only call hooks from React functions

Call hooks from functional components or custom hooks

Hooks Rules - Examples

function Component() {
    // ✅ Correct - at top level
    const [count, setCount] = useState(0);

    // ❌ Wrong - inside condition
    if (count > 0) {
        const [name, setName] = useState('');
    }

    // ❌ Wrong - inside loop
    for (let i = 0; i < 3; i++) {
        const [num, setNum] = useState(i);
    }

    // ❌ Wrong - inside event handler
    const handleClick = () => {
        const [clicked, setClicked] = useState(false);
    };
}

Practical Example: Todo App

function TodoApp() {
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState('');

    const addTodo = () => {
        if (input.trim()) {
            setTodos([...todos, { id: Date.now(), text: input }]);
            setInput('');
        }
    };

    return (
        <div>
            <input value={input}
                   onChange={(e) => setInput(e.target.value)} />
            <button onClick={addTodo}>Add</button>
            <ul>
                {todos.map(todo => (
                    <li key={todo.id}>{todo.text}</li>
                ))}
            </ul>
        </div>
    );
}

Review: Key Concepts

Today's Takeaways

You Can Now:

📅 Next Class: React Router

📝 Homework: CV Application Project