Session 2 — Events, Conditional Rendering & Lists
We'll make React apps truly interactive — responding to clicks, showing/hiding content, and rendering dynamic lists. By the end, you'll build a working todo list.
onClick, onChange, onSubmit — how React handles user interactions.
Show or hide elements based on state using if, ternary, and &&.
Render arrays of data with .map() and why keys matter.
Combine events, state, and lists into a real mini-project.
Review key concepts and test what you've learned.
In React, you attach events directly to JSX elements using camelCase props like onClick, onChange, and onSubmit.
function ButtonDemo() { // Handler is just a function function handleClick() { alert('Button was clicked!') } return ( <div> {/* Pass the function, don't call it */} <button onClick={handleClick}>Click me</button> {/* Or use an inline arrow function */} <button onClick={() => alert('Hello!')}>Inline</button> </div> ) }
Write onClick={handleClick} not onClick={handleClick()}. The second version calls the function immediately when the component renders — not on click.
Fires when the user clicks an element. Most common event in React.
Fires on every keystroke in an input. Used to track text field values.
Fires when a form is submitted. Always call e.preventDefault() inside.
Fires when an element gains or loses focus. Useful for validation feedback.
React wraps native browser events in a SyntheticEvent — it works the same way but works consistently across all browsers.
import { useState } from 'react' function InputDemo() { const [text, setText] = useState('') // e is the event object — e.target is the input element function handleChange(e) { setText(e.target.value) } return ( <div> <input value={text} onChange={handleChange} placeholder="Type something..." /> <p>You typed: {text}</p> <p>Length: {text.length} characters</p> </div> ) }
e.target.value — the current value of an input.
e.target.checked — whether a checkbox is checked.
e.preventDefault() — stops the default browser behavior (e.g., form page reload).
React lets you conditionally show parts of the UI based on state. There are three common patterns.
function ConditionalDemo() { const [isLoggedIn, setIsLoggedIn] = useState(false) const [count, setCount] = useState(0) return ( <div> {/* Pattern 1: if/else with early return */} function Status() { if (isLoggedIn) return <p>Welcome back!</p> return <p>Please log in.</p> } {/* Pattern 2: Ternary operator — use for either/or */} {isLoggedIn ? <button>Logout</button> : <button>Login</button> } {/* Pattern 3: && operator — use to show or show nothing */} {count > 5 && <p>Count is getting high!</p>} </div> ) }
| Pattern | When to Use | Example |
|---|---|---|
| if / else | Complex logic, multiple branches | Show different pages based on user role |
| Ternary ? : | Two options, inline in JSX | Login / Logout button |
| && operator | Show something or nothing | Show error message if there's an error |
Click the button to toggle the message (simulates conditional rendering):
To render a list of items, use JavaScript's .map() method. Each item needs a unique key prop so React can track changes efficiently.
const fruits = ['Apple', 'Banana', 'Cherry'] function FruitList() { return ( <ul> {fruits.map((fruit, index) => ( <li key={index}>{fruit}</li> ))} </ul> ) } // With objects — use a unique ID as key const users = [ { id: 1, name: 'Ahmed' }, { id: 2, name: 'Sara' }, ] function UserList() { return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ) }
Prefer using a real ID from your data as the key. Using array index as key works for static lists, but can cause bugs if items are added, removed, or reordered.
This is your source of truth — could be from state, props, or fetched from an API.
Each item in the array becomes a JSX element. The callback returns JSX.
React uses keys to identify which items changed, were added, or were removed.
Let's combine everything: state for the list, events for adding/removing items, and .map() for rendering them.
import { useState } from 'react' function TodoApp() { const [todos, setTodos] = useState([ { id: 1, text: 'Learn React', done: false } ]) const [input, setInput] = useState('') function addTodo() { if (!input.trim()) return setTodos([...todos, { id: Date.now(), text: input, done: false }]) setInput('') } function toggleTodo(id) { setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t )) } function deleteTodo(id) { setTodos(todos.filter(t => t.id !== id)) } return ( <div> <h2>My Todos ({todos.filter(t => !t.done).length} left)</h2> <input value={input} onChange={e => setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && addTodo()} placeholder="Add a task..." /> <button onClick={addTodo}>Add</button> <ul> {todos.map(todo => ( <li key={todo.id}> <span onClick={() => toggleTodo(todo.id)} style={{ textDecoration: todo.done ? 'line-through' : 'none' }} >{todo.text}</span> <button onClick={() => deleteTodo(todo.id)}>✕</button> </li> ))} </ul> </div> ) }
Add tasks, click to complete, hit ✕ to delete:
[...todos, newTodo] creates a new array with all existing items plus the new one. React requires new array/object references to detect state changes — never mutate state directly.
Let's review the key concepts from today's session.
React uses camelCase event props like onClick and onChange. Pass the function reference, don't call it.
Use ternary for either/or, && for show-or-nothing, and if/else for complex logic.
Transform arrays of data into arrays of JSX elements.
Every list item needs a unique key prop. Prefer IDs over array indexes.
onClick={handleClick()}?key prop on list items?In Session 3, we'll learn useEffect — how to run side effects like fetching data from APIs, handling loading states, and cleaning up effects.