Practice Questions

1. What is the difference between a JavaScript expression and a statement? Why does JSX only allow expressions inside {}? Give one example of something you cannot write directly inside JSX curly braces and show how you would achieve the same result using an expression.

Solution

An expression produces a value (e.g., 2 + 3, isActive ? "yes" : "no", items.map(fn)). A statement performs an action but does not produce a value (e.g., if, for, while). JSX curly braces are compiled into function arguments, which must be expressions.

You cannot write an if statement inside JSX:

// ❌ Won't work
<p>{if (loggedIn) { "Welcome" }}</p>

Use a ternary or && instead:

// ✅ Ternary
<p>{loggedIn ? "Welcome" : "Please sign in"}</p>

// ✅ Logical AND
<p>{loggedIn && "Welcome"}</p>

2. Write a React component called Greeting that accepts a name prop (typed with a TypeScript interface) and renders "Hello, <name>!" in a paragraph.

Solution
interface GreetingProps {
  name: string;
}

function Greeting({ name }: GreetingProps) {
  return <p>Hello, {name}!</p>;
}

export default Greeting;

The interface defines the expected prop types, and destructuring extracts name from the props object.

3. Explain what the key prop does when rendering a list with .map(). Why is using the array index as a key sometimes problematic? When is it acceptable?

Solution

The key prop helps React identify which items in a list have changed, been added, or been removed. React uses keys to match elements between renders so it can update the DOM efficiently.

Using the array index is problematic when items can be reordered, inserted, or deleted, because the index is tied to position, not identity. React may reuse the wrong DOM node, leading to bugs (e.g., input values appearing in the wrong row).

Using the index is acceptable when the list is static and will never be reordered or filtered. An example is a fixed grid of cells that always appear in the same order.

4. Given the following array, write JSX that renders each item as a <li> inside a <ul>. Use an appropriate key.

const colors = [
  { id: "c1", name: "Red" },
  { id: "c2", name: "Green" },
  { id: "c3", name: "Blue" },
];
Solution
<ul>
  {colors.map((color) => (
    <li key={color.id}>{color.name}</li>
  ))}
</ul>

Using color.id as the key is ideal because it is a stable, unique identifier from the data itself.

5. What is the difference between props and state in React? A component receives an initialCount prop and uses it to initialize state with useState. After the user interacts with the component, does changing the prop from the parent update the component’s state? Why or why not?

Solution

Props are data passed from a parent to a child. They are read-only from the child’s perspective. State is data owned and managed by the component itself. It can change over time.

When you write const [count, setCount] = useState(initialCount), the prop is used only as the initial value. After that, the state is independent. If the parent later passes a different initialCount, the state does not reset. useState only reads its argument on the first render. As soon as setCount is called, the state and the prop can hold different values.

6. Write a component called Toggle that displays a button. When clicked, it alternates between showing the text “ON” and “OFF”. Use useState to manage the state.

Solution
import { useState } from "react";

function Toggle() {
  const [isOn, setIsOn] = useState(false);

  return <button onClick={() => setIsOn(!isOn)}>{isOn ? "ON" : "OFF"}</button>;
}

export default Toggle;

The boolean state isOn drives the displayed text. Clicking the button flips the state, which triggers a re-render with the updated text.

7. You have two sibling components that both need access to the same piece of state. Describe the “lifting state up” pattern: where does the state live, how does data reach the children, and how do the children communicate changes back to the parent?

Solution

When two sibling components need shared state, you move (lift) that state to their closest common parent.

  1. State lives in the parent — the parent calls useState and owns the data.
  2. Data flows down via props — the parent passes the current state value to each child as a prop.
  3. Events flow up via callback props — the parent passes a handler function (e.g., onChange, onUpdate) as a prop. When a child needs to change the state, it calls the callback, which triggers setState in the parent, causing a re-render with the new value.

This produces React’s one-way data flow: props down, events up.

8. Consider the following two components. Refactor them so that Parent owns the state and Child is a controlled component that receives its value and a callback via props.

// Before refactoring
function Child() {
  const [text, setText] = useState("");
  return <input value={text} onChange={(e) => setText(e.target.value)} />;
}

function Parent() {
  return <Child />;
}
Solution
interface ChildProps {
  text: string;
  onTextChange: (newText: string) => void;
}

function Child({ text, onTextChange }: ChildProps) {
  return <input value={text} onChange={(e) => onTextChange(e.target.value)} />;
}

function Parent() {
  const [text, setText] = useState("");
  return <Child text={text} onTextChange={setText} />;
}

The state is lifted from Child to Parent. Child no longer calls useState — it receives the current value and a callback. This makes Child a controlled component driven entirely by its parent.

9. Why should you use a <button> element for clickable actions instead of styling a <div> with an onClick handler? Name at least two built-in behaviors you get for free with a real <button>.

Solution

A <button> element is semantic HTML. It tells the browser and assistive technologies that this element is an interactive control. Built-in behaviors include:

  1. Keyboard accessibility. A button can be focused with Tab and activated with Enter or Space without any extra code.
  2. Screen reader announcement. Assistive technologies announce it as a button, so users know it is interactive.
  3. Focus styling. Browsers provide default focus indicators for buttons.

A <div> with onClick has none of these by default. You would need to manually add tabIndex, role="button", keyboard event handlers, and ARIA attributes to match what <button> provides out of the box.

10. A component renders a button that shows only an icon (no visible text). A screen reader user navigates to this button and hears only “button” with no description. How do you fix this? Write the corrected JSX.

Solution

Add an aria-label attribute to give the button an accessible name:

<button onClick={handleDelete} aria-label="Delete item">
  🗑️
</button>

The aria-label provides a text description that screen readers will announce instead of (or in addition to) the visible content. The visual appearance stays the same, but assistive technologies now have meaningful text to read.

11. Explain what happens step by step when a user clicks a button that calls setValue(newValue) inside a React component. What triggers the UI to update?

Solution
  1. The user clicks the button, which fires the onClick handler.
  2. The handler calls setValue(newValue), which tells React that this component’s state has changed.
  3. React schedules a re-render of the component.
  4. During re-render, React calls the component function again. This time, useState returns the new value instead of the old one.
  5. The component returns new JSX based on the updated state.
  6. React compares the new JSX with the previous output (diffing) and applies only the necessary DOM updates.

Calling setValue does not mutate anything directly. It tells React to re-render, and the new UI is whatever the component function returns when it runs again with the updated state.

12. What is the difference between a component that owns its state versus one that is fully controlled by its parent? Give a scenario where you would start with local state and later decide to lift it up.

Solution

A component that owns its state calls useState internally and manages its own data. A controlled component has no internal state. It receives all data via props and reports changes through callback props.

Scenario: You build a SearchBox component that manages its own input text with useState. It works fine on its own. Later, you need to add a “Clear All Filters” button in a parent component that should also reset the search text. Since the parent cannot reach into SearchBox’s internal state, you lift the search text state up to the parent. SearchBox becomes a controlled component that receives value and onChange as props, and the parent can now reset it alongside other filters.

You lift state up when more than one place needs the same piece of state. That includes the parent itself, if the parent needs to read or change it.