DEV Community

Cover image for React Mastery Series — Part 2: JSX, Props, State & Events
Hamed Farazi
Hamed Farazi

Posted on

React Mastery Series — Part 2: JSX, Props, State & Events

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

  1. Understanding JSX
  2. JSX vs. HTML
  3. JavaScript Expressions Inside JSX
  4. JSX Conditional Rendering
  5. JSX Fragments
  6. Understanding Props
  7. Passing Props to Components
  8. Props Destructuring
  9. The children Prop
  10. Props Are Read-Only
  11. What Is State?
  12. Why Do We Need State?
  13. Understanding useState
  14. How State Updates Work
  15. Updating Objects in State
  16. Updating Arrays in State
  17. Understanding React Re-rendering
  18. Handling Events
  19. Passing Arguments to Event Handlers
  20. Event Objects
  21. Conditional Rendering in Practice
  22. Rendering Lists with map()
  23. Understanding Keys
  24. Props vs. State
  25. Practical Project: Interactive User Dashboard
  26. Common Beginner Mistakes
  27. What We Learned
  28. 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>;
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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"
);
Enter fullscreen mode Exit fullscreen mode

With JSX:

const element = <h1>Hello React</h1>;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

In JSX:

<div className="container">
  Hello
</div>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

JSX:

<button onClick={handleClick}>
  Click
</button>
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

Difference 4: Inline styles

In HTML:

<div style="background-color: red;">
  Hello
</div>
Enter fullscreen mode Exit fullscreen mode

In JSX:

<div style={{ backgroundColor: "red" }}>
  Hello
</div>
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Output:

Hello, Hamed
Age: 25
Next year: 26
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Expressions vs. Statements

This distinction is important.

JSX curly braces accept JavaScript expressions, not arbitrary statements.

An expression produces a value.

{10 + 20}
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using logical AND

function App() {
  const hasNotifications = true;

  return (
    <div>
      {hasNotifications && (
        <p>You have new notifications.</p>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

This can render 0 because JavaScript's && operator returns the first falsy operand.

A safer approach:

{count > 0 && <p>You have notifications</p>}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

5. JSX Fragments

React components must return a single React element or a fragment.

Consider:

function App() {
  return (
    <h1>Hello</h1>
    <p>Welcome</p>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

The function receives an input and produces an output.

React components work similarly.

function Greeting(props) {
  return <h1>Hello, {props.name}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Now we can pass data to it:

function App() {
  return <Greeting name="Hamed" />;
}
Enter fullscreen mode Exit fullscreen mode

The output is:

Hello, Hamed
Enter fullscreen mode Exit fullscreen mode

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"
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Props can contain:

  • Strings
  • Numbers
  • Booleans
  • Arrays
  • Objects
  • Functions
  • React elements

For example:

<UserCard
  name="Sarah"
  age={28}
  isActive={true}
  skills={["React", "JavaScript", "CSS"]}
/>
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

With destructuring:

function UserCard({ name, age }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Usage:

function App() {
  return (
    <Card>
      <h2>User Profile</h2>
      <p>Frontend Developer</p>
      <button>View Profile</button>
    </Card>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

After clicking a button:

Count: 1
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Click the button.

The console displays:

1
2
3
Enter fullscreen mode Exit fullscreen mode

But the heading still displays:

0
Enter fullscreen mode Exit fullscreen mode

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:

  1. Stores data between renders.
  2. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Let's break this down.

const [count, setCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Because useState returns an array containing two values.

Conceptually:

const result = useState(0);

const count = result[0];
const setCount = result[1];
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Now React processes the updates in sequence.

0 → 1 → 2 → 3
Enter fullscreen mode Exit fullscreen mode

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,
});
Enter fullscreen mode Exit fullscreen mode

Suppose we want to update the user's name.

A common beginner mistake is:

user.name = "Sarah";

setUser(user);
Enter fullscreen mode Exit fullscreen mode

This directly mutates the existing object.

React state should be treated as immutable.

Instead, create a new object.

setUser((previousUser) => ({
  ...previousUser,
  name: "Sarah",
}));
Enter fullscreen mode Exit fullscreen mode

Let's understand the spread operator.

{
  ...previousUser,
  name: "Sarah"
}
Enter fullscreen mode Exit fullscreen mode

It copies the existing properties into a new object and replaces the name property.

The resulting object is:

{
  name: "Sarah",
  age: 25
}
Enter fullscreen mode Exit fullscreen mode

The original object remains unchanged.


15. Updating Arrays in State

Arrays should also be treated as immutable.

Consider:

const [items, setItems] = useState([
  "Laptop",
  "Phone"
]);
Enter fullscreen mode Exit fullscreen mode

To add an item:

setItems((previousItems) => [
  ...previousItems,
  "Tablet"
]);
Enter fullscreen mode Exit fullscreen mode

To remove an item:

setItems((previousItems) =>
  previousItems.filter((item) => item !== "Phone")
);
Enter fullscreen mode Exit fullscreen mode

To update an item:

setItems((previousItems) =>
  previousItems.map((item) =>
    item === "Laptop" ? "Gaming Laptop" : item
  )
);
Enter fullscreen mode Exit fullscreen mode

Notice how JavaScript array methods work naturally with React state.

Useful methods include:

Method Common Use
map() Transform or update items
filter() Remove items
find() Find an item
slice() Create a portion of an array
concat() Create a combined array
Spread syntax Add or copy items

Avoid methods such as push(), pop(), splice(), and sort() directly on arrays stored in state because they mutate the original array.

If you need sorting, copy the array first:

const sortedItems = [...items].sort();
Enter fullscreen mode Exit fullscreen mode

16. Understanding React Re-rendering

Rendering is the process in which React calls your component to calculate the UI for the current props and state.

A component can render again when:

  • Its state changes.
  • Its parent renders again.
  • Its consumed context value changes.

Consider:

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

  console.log("Counter rendered");

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Every time the state changes, React renders the component again.

But remember:

A React render does not automatically mean that the browser DOM is completely recreated.

React compares the resulting UI with the previous one and commits the necessary changes.

This distinction is fundamental to understanding React performance.


17. Handling Events

React supports event handling through JSX attributes.

For example:

function App() {
  function handleClick() {
    console.log("Button clicked!");
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice:

onClick={handleClick}
Enter fullscreen mode Exit fullscreen mode

We pass the function itself.

We don't call it immediately.

Incorrect:

<button onClick={handleClick()}>
  Click me
</button>
Enter fullscreen mode Exit fullscreen mode

This executes the function during rendering.

React event handlers are functions that run in response to user interactions.

Common events include:

Event Purpose
onClick Mouse click
onChange Input value change
onSubmit Form submission
onMouseEnter Mouse enters an element
onMouseLeave Mouse leaves an element
onKeyDown Keyboard key pressed
onFocus Element receives focus
onBlur Element loses focus

18. Passing Arguments to Event Handlers

Sometimes we need to pass additional data to an event handler.

For example:

function App() {
  function greet(name) {
    console.log(`Hello, ${name}`);
  }

  return (
    <button onClick={() => greet("Hamed")}>
      Greet
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why use an arrow function?

Because we want to execute greet("Hamed") only when the user clicks.

The arrow function acts as a wrapper.

We can also pass an item's ID:

function deleteUser(userId) {
  console.log("Deleting user:", userId);
}

<button onClick={() => deleteUser(42)}>
  Delete
</button>
Enter fullscreen mode Exit fullscreen mode

19. Event Objects

React event handlers receive an event object.

function App() {
  function handleClick(event) {
    console.log(event);

    console.log(event.target);
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

The event object contains information about the interaction.

For example:

  • event.target: The element where the event originated.
  • event.currentTarget: The element whose handler is currently running.
  • event.preventDefault(): Prevents the browser's default action.
  • event.stopPropagation(): Stops the event from propagating further.

Preventing default behavior

Consider a form:

function App() {
  function handleSubmit(event) {
    event.preventDefault();

    console.log("Form submitted!");
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">
        Submit
      </button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Without preventDefault(), the browser normally performs its default form submission behavior.

We prevent that behavior when we want to handle submission ourselves.


20. Conditional Rendering in Practice

Let's combine state and conditional rendering.

import { useState } from "react";

function Login() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <div>
      {isLoggedIn ? (
        <h1>Welcome back!</h1>
      ) : (
        <h1>Please log in.</h1>
      )}

      <button
        onClick={() => setIsLoggedIn(!isLoggedIn)}
      >
        Toggle Login
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now the interface changes depending on the state.

The button toggles between logged-in and logged-out UI.

A safer updater when the next value depends on the previous one is:

setIsLoggedIn((previousValue) => !previousValue);
Enter fullscreen mode Exit fullscreen mode

21. Rendering Lists with map()

React works naturally with JavaScript arrays.

Suppose we have:

const products = [
  "Laptop",
  "Phone",
  "Tablet"
];
Enter fullscreen mode Exit fullscreen mode

We can render them using map():

function ProductList() {
  const products = [
    "Laptop",
    "Phone",
    "Tablet"
  ];

  return (
    <ul>
      {products.map((product) => (
        <li key={product}>
          {product}
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

The map() method creates a new array containing React elements.

This is one of the most common patterns in React development.

Rendering objects

Real applications usually work with arrays of objects.

const users = [
  { id: 1, name: "John", role: "Developer" },
  { id: 2, name: "Sarah", role: "Designer" },
  { id: 3, name: "Alex", role: "Manager" }
];
Enter fullscreen mode Exit fullscreen mode

Render them:

function UserList() {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          <h3>{user.name}</h3>
          <p>{user.role}</p>
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

22. Understanding Keys

When rendering lists, React needs a way to identify individual items.

That's why we use the key prop.

{users.map((user) => (
  <UserCard
    key={user.id}
    name={user.name}
  />
))}
Enter fullscreen mode Exit fullscreen mode

Keys help React understand:

  • Which items were added.
  • Which items were removed.
  • Which items changed.
  • Which items stayed the same.

Imagine a list:

Before:
A
B
C

After:
A
X
B
C
Enter fullscreen mode Exit fullscreen mode

Stable keys help React recognize that X was inserted rather than treating every item as a completely different item.

Why not use array indexes?

You might see:

{users.map((user, index) => (
  <UserCard key={index} name={user.name} />
))}
Enter fullscreen mode Exit fullscreen mode

This can cause problems when items are reordered, inserted, or deleted.

For example, React may associate existing component state with the wrong item.

Whenever possible, use stable unique identifiers from your data.

Important: key is not a normal prop

<UserCard key={user.id} name={user.name} />
Enter fullscreen mode Exit fullscreen mode

The key helps React identify the element.

It is not automatically available inside UserCard.

If the component needs the ID, pass it separately:

<UserCard
  key={user.id}
  id={user.id}
  name={user.name}
/>
Enter fullscreen mode Exit fullscreen mode

23. Props vs. State

This is one of the most important distinctions in React.

Both props and state influence what a component displays, but they serve different purposes.

Props State
Passed from a parent Managed by a component
Read-only from the child's perspective Updated through state setters
Used to configure components Used to remember changing data
Parent controls the value Component manages its state

Consider:

function UserCard({ name }) {
  const [isOnline, setIsOnline] = useState(false);

  return (
    <div>
      <h2>{name}</h2>

      <p>
        {isOnline ? "Online" : "Offline"}
      </p>

      <button
        onClick={() => setIsOnline((value) => !value)}
      >
        Toggle Status
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • name is a prop.
  • isOnline is state.

The parent decides which user name to pass.

The component manages its own online status.


24. Practical Project: Interactive User Dashboard

Now let's combine everything we've learned.

We'll build a small dashboard containing:

  • A user list
  • Dynamic user information
  • Online/offline status
  • A delete button
  • Conditional rendering

Step 1: Create the user data

const initialUsers = [
  {
    id: 1,
    name: "Hamed",
    role: "Frontend Developer",
    isOnline: true
  },
  {
    id: 2,
    name: "Sarah",
    role: "UI Designer",
    isOnline: false
  },
  {
    id: 3,
    name: "Alex",
    role: "Backend Developer",
    isOnline: true
  }
];
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the UserCard component

function UserCard({
  user,
  onDelete,
  onToggleStatus
}) {
  return (
    <div className="user-card">
      <h2>{user.name}</h2>

      <p>{user.role}</p>

      <p>
        Status: {user.isOnline ? "Online" : "Offline"}
      </p>

      <button
        onClick={() => onToggleStatus(user.id)}
      >
        Toggle Status
      </button>

      <button
        onClick={() => onDelete(user.id)}
      >
        Delete User
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice that the component doesn't directly modify the user.

Instead, it receives callback functions from its parent.

Step 3: Create the Dashboard component

import { useState } from "react";

function Dashboard() {
  const [users, setUsers] = useState(initialUsers);

  function deleteUser(userId) {
    setUsers((previousUsers) =>
      previousUsers.filter(
        (user) => user.id !== userId
      )
    );
  }

  function toggleUserStatus(userId) {
    setUsers((previousUsers) =>
      previousUsers.map((user) =>
        user.id === userId
          ? { ...user, isOnline: !user.isOnline }
          : user
      )
    );
  }

  return (
    <div>
      <h1>User Dashboard</h1>

      <p>Total users: {users.length}</p>

      {users.length === 0 ? (
        <p>No users available.</p>
      ) : (
        users.map((user) => (
          <UserCard
            key={user.id}
            user={user}
            onDelete={deleteUser}
            onToggleStatus={toggleUserStatus}
          />
        ))
      )}
    </div>
  );
}

export default Dashboard;
Enter fullscreen mode Exit fullscreen mode

Step 4: Use Dashboard in App

import Dashboard from "./Dashboard";

function App() {
  return <Dashboard />;
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Congratulations!

You've just built an interactive React application using:

  • JSX
  • Components
  • Props
  • State
  • Events
  • Conditional rendering
  • Array methods
  • Keys
  • Component composition

This is the foundation of many real-world React applications.


25. Common Beginner Mistakes

Mistake 1: Calling an event handler during rendering

Incorrect:

<button onClick={handleClick()}>
  Click
</button>
Enter fullscreen mode Exit fullscreen mode

Correct:

<button onClick={handleClick}>
  Click
</button>
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Mutating state directly

Incorrect:

users.push(newUser);
setUsers(users);
Enter fullscreen mode Exit fullscreen mode

Correct:

setUsers((previousUsers) => [
  ...previousUsers,
  newUser
]);
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Forgetting keys

Incorrect:

users.map((user) => (
  <li>{user.name}</li>
))
Enter fullscreen mode Exit fullscreen mode

Correct:

users.map((user) => (
  <li key={user.id}>{user.name}</li>
))
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Expecting state to update immediately

Incorrect mental model:

setCount(count + 1);

console.log(count);
Enter fullscreen mode Exit fullscreen mode

The log still sees the current render's state snapshot.

Mistake 5: Modifying props

Props should be treated as read-only.

If data needs to change, update the owning state and pass the new value down.

Mistake 6: Using normal variables for UI state

Ordinary variables don't trigger React renders.

Use useState when a value needs to affect the rendered UI.

Mistake 7: Using index as a key everywhere

Indexes are not always stable identifiers.

Prefer unique IDs from your data.


26. What We Learned

In this article, we explored the concepts that make React applications dynamic.

We learned that:

  • JSX describes UI using JavaScript-friendly syntax.
  • Props allow parent components to pass data to children.
  • State allows components to remember changing information.
  • useState manages state in function components.
  • State updates schedule new renders.
  • Event handlers respond to user interactions.
  • Conditional rendering displays UI based on conditions.
  • map() renders collections of elements.
  • Keys help React identify list items.
  • Immutable updates help React manage state correctly.

Most importantly, we learned how props, state, and events work together.

User Interaction
       ↓
Event Handler
       ↓
State Update
       ↓
React Render
       ↓
Updated UI
Enter fullscreen mode Exit fullscreen mode

This is one of the fundamental patterns you'll use throughout your React career.


27. What's Next?

In Part 3: React Hooks — Complete Practical Guide, we'll explore Hooks in much greater depth.

We'll cover:

  • What Hooks are and why they exist
  • Rules of Hooks
  • useState in depth
  • useEffect and dependency arrays
  • Effect cleanup
  • useRef and DOM references
  • useContext and Context API
  • useReducer
  • useMemo
  • useCallback
  • useId
  • useTransition
  • useDeferredValue
  • Custom Hooks
  • Common Hook mistakes
  • Practical projects

We'll also explore how React handles component rendering and why understanding Hook behavior is essential for writing reliable applications.


Continue the React Mastery Series

Part 1: React Fundamentals — From Zero to Components

Part 2: JSX, Props, State & Events

Part 3: React Hooks — Complete Practical Guide

Part 4: React Forms & API Integration

Part 5: React Architecture & Best Practices

Part 6: React Performance Optimization

Part 7: React Internals — Virtual DOM, Fiber & Reconciliation

Part 8: Modern React 19 Features

Part 9: Routing & State Management

Part 10: Testing React Applications


If you're learning React, don't just read the examples.

Run them, change them, break them, and try to understand why they behave the way they do.

That's how you move from knowing React syntax to actually understanding React.

Happy coding!

React #JavaScript #FrontendDevelopment #WebDevelopment #ReactJS

Top comments (0)