Welcome back to the React Mastery Series!
In Part 1, we explored the fundamentals of React, learned about components, understood declarative programming, and created our first React application.
Now it's time to make our components interactive.
A React application isn't just a collection of static components. It needs to respond to user interactions, display dynamic information, and update its interface whenever data changes.
In this article, we'll explore four fundamental concepts that make this possible:
- JSX
- Props
- State
- Events
We'll also cover conditional rendering, lists, keys, and how React handles state updates.
By the end of this article, you'll understand how to build dynamic React components and how data flows through a React application.
Table of Contents
- Understanding JSX
- JSX vs. HTML
- JavaScript Expressions Inside JSX
- JSX Conditional Rendering
- JSX Fragments
- Understanding Props
- Passing Props to Components
- Props Destructuring
- The children Prop
- Props Are Read-Only
- What Is State?
- Why Do We Need State?
- Understanding useState
- How State Updates Work
- Updating Objects in State
- Updating Arrays in State
- Understanding React Re-rendering
- Handling Events
- Passing Arguments to Event Handlers
- Event Objects
- Conditional Rendering in Practice
- Rendering Lists with map()
- Understanding Keys
- Props vs. State
- Practical Project: Interactive User Dashboard
- Common Beginner Mistakes
- What We Learned
- What's Next?
1. Understanding JSX
JSX stands for JavaScript XML.
It is a syntax extension for JavaScript that allows developers to describe UI using HTML-like syntax.
For example:
const element = <h1>Hello React!</h1>; At first glance, this looks like HTML.
However, JSX is not HTML.
It is a syntax that gets transformed into JavaScript by tools such as Vite's build pipeline.
Let's look at a component:
function App() { return ( <div> <h1>Hello React</h1> <p>Welcome to the application.</p> </div> ); } The component returns JSX describing the interface.
React uses this description to determine what should appear on the screen.
Why JSX?
Without JSX, you can create React elements using JavaScript:
import { createElement } from "react"; const element = createElement( "h1", null, "Hello React" ); With JSX:
const element = <h1>Hello React</h1>; JSX makes component structures easier to read and write.
2. JSX vs. HTML
Although JSX looks similar to HTML, there are important differences.
Difference 1: className instead of class
In HTML:
<div class="container"> Hello </div> In JSX:
<div className="container"> Hello </div> React uses className because class is a reserved JavaScript keyword.
Difference 2: camelCase attributes
Many JSX attributes use camelCase.
HTML:
<button onclick="handleClick()"> Click </button> JSX:
<button onClick={handleClick}> Click </button> Notice the uppercase C in onClick.
Difference 3: Self-closing tags
In JSX, elements must be properly closed.
<img src="/logo.png" alt="Logo" /> <input type="text" /> Difference 4: Inline styles
In HTML:
<div style="background-color: red;"> Hello </div> In JSX:
<div style={{ backgroundColor: "red" }}> Hello </div> Why are there two sets of curly braces?
- The outer braces indicate a JavaScript expression.
- The inner braces define a JavaScript object.
3. JavaScript Expressions Inside JSX
One of the most useful JSX features is the ability to embed JavaScript expressions using curly braces.
function App() { const name = "Hamed"; const age = 25; return ( <div> <h1>Hello, {name}</h1> <p>Age: {age}</p> <p>Next year: {age + 1}</p> </div> ); } Output:
Hello, Hamed Age: 25 Next year: 26 You can use:
- Variables
- Mathematical expressions
- Function calls
- Ternary expressions
- Array methods
- Object properties
For example:
function App() { const products = ["Laptop", "Phone", "Tablet"]; return ( <div> <p>Total products: {products.length}</p> <p>{products.join(", ")}</p> </div> ); } Expressions vs. Statements
This distinction is important.
JSX curly braces accept JavaScript expressions, not arbitrary statements.
An expression produces a value.
{10 + 20} A statement performs an action or controls program flow.
For example, you cannot directly write an if statement inside JSX:
// Incorrect return ( <div> {if (isLoggedIn) { "Welcome"; }} </div> ); Instead, use a ternary expression or calculate the result before returning JSX.
4. JSX Conditional Rendering
Sometimes the UI should change depending on a condition.
For example:
- Show a login button if the user is logged out.
- Show a dashboard if the user is logged in.
- Display an error message if something goes wrong.
React supports several approaches.
Using the ternary operator
function App() { const isLoggedIn = true; return ( <div> {isLoggedIn ? ( <h1>Welcome back!</h1> ) : ( <h1>Please log in.</h1> )} </div> ); } Using logical AND
function App() { const hasNotifications = true; return ( <div> {hasNotifications && ( <p>You have new notifications.</p> )} </div> ); } The expression after && is rendered only when the condition is truthy.
Be careful with numbers:
const count = 0; return ( <div> {count && <p>You have notifications</p>} </div> ); This can render 0 because JavaScript's && operator returns the first falsy operand.
A safer approach:
{count > 0 && <p>You have notifications</p>} Using if statements
For more complex conditions, use regular JavaScript before returning JSX.
function Greeting({ isLoggedIn }) { if (isLoggedIn) { return <h1>Welcome back!</h1>; } return <h1>Please log in.</h1>; } 5. JSX Fragments
React components must return a single React element or a fragment.
Consider:
function App() { return ( <h1>Hello</h1> <p>Welcome</p> ); } This is invalid because there are two adjacent JSX elements without a parent.
One solution is to use a div:
function App() { return ( <div> <h1>Hello</h1> <p>Welcome</p> </div> ); } But sometimes we don't want an additional DOM element.
That's where Fragments come in.
function App() { return ( <> <h1>Hello</h1> <p>Welcome</p> </> ); } Fragments group elements without adding an extra DOM node.
You can also explicitly import Fragment:
import { Fragment } from "react"; function App() { return ( <Fragment> <h1>Hello</h1> <p>Welcome</p> </Fragment> ); } 6. Understanding Props
Props stands for Properties.
Props allow a parent component to pass data to a child component.
Think of props as the inputs of a component.
Imagine a function:
function greet(name) { return `Hello, ${name}`; } console.log(greet("Hamed")); The function receives an input and produces an output.
React components work similarly.
function Greeting(props) { return <h1>Hello, {props.name}</h1>; } Now we can pass data to it:
function App() { return <Greeting name="Hamed" />; } The output is:
Hello, Hamed The parent component provides the data.
The child component receives it through props.
Passing multiple props
function UserCard(props) { return ( <div> <h2>{props.name}</h2> <p>Age: {props.age}</p> <p>Role: {props.role}</p> </div> ); } function App() { return ( <UserCard name="Sarah" age={28} role="Frontend Developer" /> ); } Props can contain:
- Strings
- Numbers
- Booleans
- Arrays
- Objects
- Functions
- React elements
For example:
<UserCard name="Sarah" age={28} isActive={true} skills={["React", "JavaScript", "CSS"]} /> 7. Props Destructuring
Instead of repeatedly writing props.name, you can use JavaScript destructuring.
Without destructuring:
function UserCard(props) { return ( <div> <h2>{props.name}</h2> <p>{props.age}</p> </div> ); } With destructuring:
function UserCard({ name, age }) { return ( <div> <h2>{name}</h2> <p>{age}</p> </div> ); } This is cleaner and more readable.
You can also provide default values:
function UserCard({ name = "Anonymous", role = "Developer" }) { return ( <div> <h2>{name}</h2> <p>{role}</p> </div> ); } If a parent doesn't provide these props, the default values are used.
8. The children Prop
React has a special prop called children.
It represents the content placed between a component's opening and closing tags.
function Card({ children }) { return ( <div className="card"> {children} </div> ); } Usage:
function App() { return ( <Card> <h2>User Profile</h2> <p>Frontend Developer</p> <button>View Profile</button> </Card> ); } Everything between <Card> and </Card> becomes the children prop.
This is particularly useful for building reusable layouts, modals, cards, and containers.
9. Props Are Read-Only
This is an important React rule.
A child component should never directly modify its props.
Incorrect:
function UserCard(props) { props.name = "Updated Name"; return <h1>{props.name}</h1>; } Props should be treated as immutable.
Why?
Because the parent owns the data it passes to the child.
The child receives that data but doesn't own the original value.
If the data needs to change, the parent should update it and pass the new value down.
We'll see how this works with state.
10. What Is State?
State is data that a component remembers between renders.
Unlike ordinary local variables, state is managed by React.
When state changes, React schedules a new render so the UI can reflect the updated value.
Imagine a counter.
Initially:
Count: 0 After clicking a button:
Count: 1 The displayed value changes.
How does React know that it needs to update the interface?
We use a Hook called useState.
11. Why Do We Need State?
Let's try implementing a counter using a normal variable.
function Counter() { let count = 0; function increment() { count++; console.log(count); } return ( <div> <h1>{count}</h1> <button onClick={increment}> Increment </button> </div> ); } Click the button.
The console displays:
1 2 3 But the heading still displays:
0 Why?
Because changing an ordinary JavaScript variable doesn't tell React to render the component again.
Also, whenever the component renders again, its local variables are initialized again.
We need something that:
- Stores data between renders.
- Notifies React when that data changes.
That's exactly what state provides.
12. Understanding useState
useState is a React Hook that allows function components to manage state.
import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return ( <div> <h1>Count: {count}</h1> <button onClick={() => setCount(count + 1)}> Increment </button> </div> ); } Let's break this down.
const [count, setCount] = useState(0); There are three important parts:
1. count
The current state value.
2. setCount
The function used to request a state update.
3. useState(0)
The initial state value.
The initial value is used when React initializes that state.
When the user clicks the button:
setCount(count + 1); React schedules a state update.
The component renders again using the updated state.
The array destructuring
Why do we write:
const [count, setCount] = useState(0); Because useState returns an array containing two values.
Conceptually:
const result = useState(0); const count = result[0]; const setCount = result[1]; Array destructuring simply makes this easier to read.
13. How State Updates Work
One of the most important things to understand about React is that state is a snapshot for a particular render.
Consider:
function Counter() { const [count, setCount] = useState(0); function increment() { setCount(count + 1); } return ( <button onClick={increment}> {count} </button> ); } When the component renders with count = 0, the event handler created during that render sees that value.
Calling setCount requests an update.
React then renders the component with the new state.
What happens if we update state multiple times?
Consider:
function incrementThreeTimes() { setCount(count + 1); setCount(count + 1); setCount(count + 1); } You might expect the count to increase by three.
But if the current value is 0, all three calls use the same render snapshot.
They effectively request:
setCount(1); setCount(1); setCount(1); The final value is 1, not 3.
The functional updater form
To solve this, use a function that receives the previous state.
function incrementThreeTimes() { setCount((previousCount) => previousCount + 1); setCount((previousCount) => previousCount + 1); setCount((previousCount) => previousCount + 1); } Now React processes the updates in sequence.
0 → 1 → 2 → 3 This form is especially useful whenever the next state depends on the previous state.
14. Updating Objects in State
State can contain objects.
const [user, setUser] = useState({ name: "Hamed", age: 25, }); Suppose we want to update the user's name.
A common beginner mistake is:
user.name = "Sarah"; setUser(user); This directly mutates the existing object.
React state should be treated as immutable.
Instead, create a new object.
setUser((previousUser) => ({ ...previousUser, name: "Sarah", })); Let's understand the spread operator.
{ ...previousUser, name: "Sarah" } It copies the existing properties into a new object and replaces the name property.
The resulting object is:
Related posts
Re-Implementing :nth-child() With Modern CSS
Building ArewaSafe: How We Used Gemma 4 & Disguise UX to Protect Highway Commuters in Northern Nigeria
Transactional Email Delivery in WordPress
Unique Pageviews in Google Analytics 4 (GA4)
