Web Development using JavaScript/React

Chapter 6
React Foundations and Component Design

Bachelors Program in IT
Web Development using JavaScript/React   •   Bachelor

React Foundations and Component Design

Introduction

Modern web interfaces are interactive systems rather than collections of independent documents. A user may open a dashboard, filter data, submit a form, expand a panel, and navigate between views without loading an entirely new page. React is a JavaScript library for building such interfaces from reusable components.

React’s central idea is declarative UI. Instead of manually instructing the browser how to change individual elements, a developer describes what the interface should look like for a given application state. React then coordinates the necessary updates. This approach supports modularity, predictable data flow, and efficient development of large user interfaces.

This chapter introduces JSX, components, props, state, events, conditional rendering, lists, composition, and lifecycle concepts. The examples use contemporary function components and Hooks, while also explaining lifecycle ideas that apply more broadly.

Learning Objectives

By the end of this chapter, you should be able to:

  • explain the difference between imperative and declarative UI programming;
  • describe the role of React’s virtual representation of the DOM;
  • create reusable function components with JSX;
  • distinguish props from state and apply one-way data flow;
  • handle user events safely;
  • render content conditionally and render collections with stable keys;
  • use composition to design flexible components;
  • explain rendering, mounting, updating, and unmounting;
  • identify common design errors and apply professional React practices.

1. Imperative and Declarative Interface Design

In an imperative approach, code specifies a sequence of operations. For example, a program might find a paragraph, change its text, add a CSS class, and remove an old element when a condition changes. The programmer must maintain the correspondence between application data and the visible DOM.

A declarative approach specifies the desired result. A component can express:

function Greeting({ signedIn, name }) {
  return signedIn ? <h1>Welcome, {name}</h1> : <h1>Please sign in</h1>;
}

The component does not explain how to locate an existing h1 or replace its text. It describes the UI associated with the current values of signedIn and name. When those values change, React re-renders the component and updates the browser as needed.

Declarative programming does not eliminate computation or state transitions. Rather, it moves responsibility for DOM synchronization from application code to the UI framework. This separation reduces accidental complexity, especially when many controls affect the same screen.

The virtual DOM and reconciliation

The browser maintains a Document Object Model (DOM), a tree representation of the document. Direct DOM operations can be expensive or difficult to coordinate when an application changes frequently. React components produce a lightweight JavaScript representation of the intended UI, commonly called the virtual DOM.

After a render, React compares the new element tree with the previous one. This comparison is often called reconciliation. React then commits the required changes to the actual DOM. The virtual DOM is therefore not a second browser document and does not automatically make every application faster. Its main value is that it enables a systematic model for determining updates and supports React’s declarative programming model.

React rendering and reconciliation flow Props + Statecurrent inputs RenderReact elements Commitactual DOM A user event or external update changes inputs
React rendering and reconciliation flow
Figure: React rendering and reconciliation flow

Figure: Conceptual flow from component inputs through rendering and DOM commitment.

React’s reconciliation is an implementation mechanism, not a license to ignore algorithmic cost. Large lists, unnecessary renders, unstable keys, and expensive calculations can still harm performance. Developers should first design clear state and component boundaries, then measure performance before applying optimizations.

2. JSX and React Elements

JSX is a syntax extension that allows HTML-like markup inside JavaScript. It is transformed by a build tool into calls that create React elements. JSX is not HTML embedded directly in the browser.

const title = <h1 className="page-title">Course Catalogue</h1>;

Important JSX rules include:

  1. A component must return one enclosing element or a fragment.
  2. JavaScript expressions are placed in braces: {expression}.
  3. Most attributes use JavaScript naming conventions, such as className and htmlFor.
  4. Tags must be closed: <img alt="Logo" />.
  5. Values supplied by JavaScript should not be constructed by unsafe string concatenation.

A fragment avoids adding an unnecessary DOM element:

function Contact() {
  return (
    <>
      <h2>Contact</h2>
      <p>Send us a message.</p>
    </>
  );
}

JSX improves readability, but it must not be confused with a complete component. A React element is a description of what should be rendered; a component is a reusable function or class that produces elements.

3. Components and Reusability

A component is a self-contained unit of UI structure and behavior. Function components are ordinary JavaScript functions whose names conventionally begin with an uppercase letter.

function Button({ children, onClick, disabled = false }) {
  return (
    <button type="button" onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
}

This component is reusable because its label, action, and disabled condition are supplied from outside. A component should generally have a clear responsibility. A component that fetches data, validates a complex form, controls layout, and renders every page element is difficult to test and change.

Good component design considers:

  • cohesion: related markup, behavior, and state belong together;
  • coupling: components should depend on as few implementation details as possible;
  • interface clarity: props should have meaningful names and predictable types;
  • accessibility: semantic HTML and keyboard behavior should be built into the component;
  • reuse at the right level: not every small fragment needs to become a separate component.

4. Props and One-Way Data Flow

Props are read-only inputs passed from a parent component to a child. They may contain strings, numbers, objects, arrays, functions, or React elements.

function ProductCard({ product, onAdd }) {
  return (
    <article>
      <h2>{product.name}</h2>
      <p>£{product.price}</p>
      <button onClick={() => onAdd(product.id)}>Add to basket</button>
    </article>
  );
}

The parent owns the product data and supplies a callback. The child does not directly modify the parent’s data. This is called one-way data flow: data moves down through props, while events are communicated upward through callback props.

Props should not be mutated. If a child needs a modified version, it should derive one during rendering or request a state update from its owner. Mutating an object received through props can cause updates to become difficult to predict because several components may hold references to the same object.

Prop validation and contracts

JavaScript does not automatically enforce a component’s prop contract. Teams may use TypeScript, runtime validation libraries, or documented conventions. Regardless of tooling, a component should make expectations clear. For example, a product card may require a product object with an identifier, display name, and non-negative price.

5. State and Events

State is data managed by a component or one of its ancestors that can change over time and affect rendering. The useState Hook provides state in a function component.

import { useState } from 'react';

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

  function increment() {
    setCount(previousCount => previousCount + 1);
  }

  return (
    <button type="button" onClick={increment}>
      Count: {count}
    </button>
  );
}

Calling the setter schedules a new render; it does not immediately replace the local variable in the currently executing function. The functional update form is important when the next value depends on the previous value, particularly when several updates may be scheduled together.

State should be updated immutably. For an array:

setTasks(previousTasks =>
  previousTasks.map(task =>
    task.id === id ? { ...task, complete: !task.complete } : task
  )
);

For an object:

setProfile(previousProfile => ({
  ...previousProfile,
  city: 'Leeds'
}));

The spread syntax creates a new object or array reference. It is a shallow copy, so nested structures require appropriate copying at each changed level.

Events

React event handlers are passed as functions rather than called during rendering:

<button onClick={handleSave}>Save</button>

Writing onClick={handleSave()} calls the function while rendering and usually produces incorrect behavior. To pass an argument, use a function expression:

<button onClick={() => removeItem(item.id)}>Remove</button>

Form controls commonly use controlled state:

function SearchBox({ value, onChange }) {
  return (
    <label>
      Search
      <input value={value} onChange={event => onChange(event.target.value)} />
    </label>
  );
}

The browser event object supplies information such as event.target.value. Validation should be understandable to users, should not rely solely on color, and should preserve entered data when possible.

6. Conditional Rendering

React supports ordinary JavaScript conditional expressions. A ternary is useful when there are two alternatives:

return isLoading ? <p>Loading…</p> : <Results items={items} />;

Logical AND is suitable when content appears only if a condition is true:

{errorMessage && <p role="alert">{errorMessage}</p>}

Developers must be careful with numeric values. If count is zero, {count && <p>...</p>} can render 0, because zero is returned by the expression. An explicit comparison is safer:

{count > 0 && <p>{count} items selected.</p>}

Conditional rendering should preserve semantic structure and accessibility. A loading message should communicate status, and an empty data set should explain what the user can do next rather than showing a blank region.

7. Lists and Keys

Collections are commonly rendered with map:

function TaskList({ tasks, onToggle }) {
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <label>
            <input
              type="checkbox"
              checked={task.complete}
              onChange={() => onToggle(task.id)}
            />
            {task.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

A key gives React a stable identity for each sibling in a list. Prefer a unique identifier from the data. Array indexes are acceptable only when the list is static and never reordered, inserted into, or deleted from. Using indexes in a dynamic list can cause state or focus to appear attached to the wrong item after changes.

Keys are used by React internally; they are not automatically available as component props. If a child needs an identifier, pass it explicitly.

8. Composition and Component Architecture

Composition means assembling components by passing elements or components into other components. It is often more flexible than creating a large component with many mode-specific props.

function Panel({ title, children, footer }) {
  return (
    <section className="panel">
      <header><h2>{title}</h2></header>
      <div className="panel-body">{children}</div>
      {footer && <footer>{footer}</footer>}
    </section>
  );
}

function AccountPanel() {
  return (
    <Panel
      title="Account"
      footer={<button type="button">Edit</button>}
    >
      <p>Signed in as [email protected]</p>
    </Panel>
  );
}

The children prop represents nested content. Named slots, such as footer, clarify multiple insertion points. Composition supports separation of concerns: a panel controls layout, while its caller controls the panel’s particular content.

A practical hierarchy often contains:

  • page or route components, which coordinate a screen;
  • feature components, which represent a domain function such as checkout;
  • presentational components, which render supplied data;
  • primitive components, such as buttons, labels, and dialogs.

These are design heuristics, not rigid categories. State should be placed at the lowest common ancestor of the components that need it. Moving state upward is called lifting state up. If unrelated branches require the same state, context or a state-management library may be appropriate, but global state should not be used merely to avoid understanding ownership.

9. Rendering and Lifecycle Concepts

A component’s lifecycle is commonly described in three phases:

  1. Mounting: the component is created and inserted into the DOM.
  2. Updating: props or state change, causing a new render and possible DOM updates.
  3. Unmounting: the component is removed from the DOM.

Function components do not usually contain lifecycle methods named after these phases. Instead, Hooks express side effects. The useEffect Hook is used for synchronization with systems outside React, such as subscriptions, timers, browser APIs, or network-related coordination.

import { useEffect } from 'react';

function WindowTitle({ title }) {
  useEffect(() => {
    const previousTitle = document.title;
    document.title = title;

    return () => {
      document.title = previousTitle;
    };
  }, [title]);

  return <h1>{title}</h1>;
}

The effect runs after a committed render when title changes. The returned function is cleanup: it runs before the effect is re-run and when the component unmounts. Effects should be limited to genuine side effects. Calculating a value from props during rendering usually does not require an effect.

A dependency array documents the reactive values used by the effect. Omitting a required dependency can create stale values; adding unstable values can cause repeated execution. Modern development configurations may deliberately run effects more than once during development to expose unsafe assumptions. Effects should therefore be written with correct setup and cleanup, not with mechanisms that merely suppress repeated execution.

Data fetching and cancellation

Network requests involve asynchronous work and possible unmounting. A robust design handles loading, success, error, and cancellation or stale responses. In production systems, a dedicated data-fetching library may provide caching, retries, and synchronization. A component should not update visible state from a response that no longer corresponds to the current request.

10. Worked Application: An Accessible Task Board

Consider an illustrative university task board. Students can add tasks, mark them complete, filter the list, and receive an empty-state message. The design begins by identifying state rather than beginning with DOM operations.

Background and problem

A student-support portal needs a task board that remains usable as tasks are added and filtered. A first implementation stores the entire displayed HTML string and manually edits rows. This produces duplicated logic and can leave checkboxes inconsistent with the underlying data.

Analysis

The domain state is an array of task objects and a filter value. The displayed list is derived from those values; it does not need to be stored separately. The parent owns state because adding, filtering, and toggling all affect the same list.

Approach

import { useMemo, useState } from 'react';

function TaskBoard({ initialTasks = [] }) {
  const [tasks, setTasks] = useState(initialTasks);
  const [filter, setFilter] = useState('all');
  const [draft, setDraft] = useState('');

  const visibleTasks = useMemo(() => {
    if (filter === 'active') return tasks.filter(task => !task.complete);
    if (filter === 'complete') return tasks.filter(task => task.complete);
    return tasks;
  }, [tasks, filter]);

  function addTask(event) {
    event.preventDefault();
    const title = draft.trim();
    if (!title) return;

    setTasks(previous => [
      ...previous,
      { id: crypto.randomUUID(), title, complete: false }
    ]);
    setDraft('');
  }

  function toggleTask(id) {
    setTasks(previous => previous.map(task =>
      task.id === id ? { ...task, complete: !task.complete } : task
    ));
  }

  return (
    <section aria-labelledby="tasks-heading">
      <h2 id="tasks-heading">Study tasks</h2>
      <form onSubmit={addTask}>
        <label htmlFor="new-task">New task</label>
        <input
          id="new-task"
          value={draft}
          onChange={event => setDraft(event.target.value)}
        />
        <button type="submit">Add task</button>
      </form>

      <label htmlFor="task-filter">Show</label>
      <select id="task-filter" value={filter} onChange={event => setFilter(event.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="complete">Complete</option>
      </select>

      {visibleTasks.length === 0 ? (
        <p role="status">No tasks match this filter.</p>
      ) : (
        <ul>
          {visibleTasks.map(task => (
            <li key={task.id}>
              <label>
                <input
                  type="checkbox"
                  checked={task.complete}
                  onChange={() => toggleTask(task.id)}
                />
                {task.title}
              </label>
            </li>
          ))}
        </ul>
      )}
    </section>
  );
}

Outcome

The interface has a single source of truth for tasks. The visible list is derived from state, each task has a stable key, and updates create new arrays and objects rather than mutating existing data. The form prevents empty tasks, the select control is controlled, and the empty result is communicated to assistive technology.

Learning points

The example demonstrates state ownership, derived data, immutable updates, controlled inputs, event handling, conditional rendering, stable keys, and semantic accessibility. useMemo is used here to express a derived calculation; it should not be assumed to provide a benefit for every small array. For a very small list, direct filtering during render may be clearer.

11. Common Errors and Professional Practice

Mutating state

Code such as tasks.push(newTask) changes an existing array without creating a new state value. Use a setter and create a new array. Immutability makes change detection and reasoning more reliable.

Overusing effects

An effect is not a general-purpose way to calculate derived values or respond to every event. Calculate derivable values during rendering and perform event-specific actions in event handlers. Use effects for synchronization with external systems.

Unstable or inappropriate keys

Do not generate a new random key during every render. That makes React treat every item as new and can destroy input state. Use stable identifiers associated with the data.

Excessive prop drilling

Passing props through many unrelated layers can indicate that a boundary is misplaced. Composition, lifting state to a suitable owner, or context may help. Context should be scoped and documented because it can make data dependencies less visible.

Ignoring accessibility

Interactive elements should use native controls where possible. Labels must be associated with inputs, keyboard interaction must work, focus must remain understandable, and status or error messages should be announced appropriately. Visual styling is not a substitute for semantic HTML.

Security and privacy

React escapes ordinary text inserted through JSX, which reduces many injection risks, but developers must still treat external data as untrusted. Avoid unsafe HTML insertion unless content has been rigorously sanitized and the security implications are understood. Do not place secrets in client-side code; browser-delivered JavaScript can be inspected by users.

12. Contemporary React Practice

Contemporary React applications may use server rendering, streaming, code splitting, and concurrent rendering capabilities. These features change where work occurs and how loading states are designed, but they do not replace the foundations in this chapter. Components still need clear inputs, correctly owned state, stable identity, and accessible output.

Development tools can inspect component trees, props, state, and render activity. Automated tests should verify observable behavior—for example, that a user can add and complete a task—rather than testing private implementation details. Static analysis, type checking, and formatting tools reduce preventable defects, while performance profiling identifies actual bottlenecks.

AI-assisted coding can generate plausible React code, but generated code must be reviewed for dependency-array errors, unsafe HTML, missing keys, incorrect state mutation, accessibility defects, and data-privacy problems. Professional responsibility remains with the developer and organization deploying the software.

Chapter Summary

React provides a declarative model in which components describe UI from props and state. JSX expresses React elements using JavaScript-friendly syntax. Props are read-only inputs passed down a component hierarchy, while state represents changing data owned by an appropriate component. Events communicate user actions, commonly through callback props, and state updates trigger new renders.

Conditional expressions control what is displayed; map renders collections; stable keys preserve item identity; and composition allows flexible component assembly. Lifecycle concepts—mounting, updating, and unmounting—are expressed in function components primarily through rendering and carefully designed effects with cleanup. Professional React development also requires immutable updates, accessible HTML, secure handling of untrusted data, testing, and measured optimization.

Key Terms

Declarative UI: Describing the desired interface for a given state rather than issuing DOM commands.

Imperative UI: A programming style that explicitly instructs the system which operations to perform.

Component: A reusable unit of UI structure and behavior.

JSX: JavaScript syntax extension used to describe React elements.

React element: An object-like description of a piece of UI.

Virtual DOM: A lightweight representation of intended UI used during React’s rendering process.

Reconciliation: Comparing rendered element structures to determine required updates.

Props: Read-only inputs passed from a parent component to a child.

State: Changeable data that influences a component’s rendering.

Hook: A React function API that enables capabilities such as state and effects in function components.

Event handler: A function invoked in response to a user or browser event.

Controlled component: A form element whose value is managed by React state or props.

Conditional rendering: Selecting UI output according to a condition.

Key: A stable identity assigned to an item in a rendered collection.

Composition: Building a component by supplying nested or named content to another component.

Mounting: Creating a component and inserting its output into the DOM.

Unmounting: Removing a component from the DOM.

Effect: Code that synchronizes a component with an external system after rendering.

Immutable update: Creating new data structures rather than modifying existing state directly.

One-way data flow: The movement of data from parent to child through props, with events communicated through callbacks.

Review Questions

Short Answer Questions

  1. What is the principal difference between declarative and imperative UI programming?
  2. Why does React require stable keys when rendering lists?
  3. Distinguish props from state.
  4. What is the purpose of the cleanup function returned by an effect?
  5. What does the children prop represent?

Long Answer Questions

  1. Explain React’s rendering and reconciliation process, including the role and limitations of the virtual DOM.
  2. Discuss how one-way data flow supports maintainability in a component hierarchy.
  3. Explain immutable state updates for objects and arrays, with suitable code examples.
  4. Compare conditional rendering, list rendering, and composition as component-design techniques.
  5. Describe the mounting, updating, and unmounting phases of a function component and relate them to effects.

Analytical / Application Questions

  1. A dynamic list uses array indexes as keys, and each row contains an editable input. Analyze the likely problem after inserting an item at the beginning and propose a correction.
  2. A component stores both tasks and visibleTasks in state. Explain why this may create inconsistency and redesign the state model.
  3. Design the component boundaries and state ownership for a shopping basket containing a product list, quantity controls, a basket summary, and a checkout form. Justify where data and callbacks should reside.

Multiple Choice Questions with Answers

Question 1

Which statement best describes declarative UI programming in React?

A. It specifies the desired UI for a given state.

B. It requires manually changing every DOM node.

C. It prevents components from receiving data.

D. It stores all interface data in global variables.

Correct Answer: A. It specifies the desired UI for a given state.

Question 2

What is the primary purpose of a key when rendering a React list?

A. To style each list item.

B. To provide stable identity for reconciliation.

C. To pass an identifier automatically as a prop.

D. To encrypt the list item’s data.

Correct Answer: B. To provide stable identity for reconciliation.

Question 3

Which update correctly toggles a task without mutating the existing array?

A. tasks.push(task); setTasks(tasks)

B. setTasks(tasks.reverse())

C. setTasks(previous => previous.map(t => t.id === id ? { ...t, complete: !t.complete } : t))

D. task.complete = !task.complete

Correct Answer: C. setTasks(previous => previous.map(t => t.id === id ? { ...t, complete: !t.complete } : t))

Question 4

What is the usual direction of data flow through React props?

A. Child to parent only

B. Parent to child

C. Sibling to browser DOM directly

D. Browser DOM to server automatically

Correct Answer: B. Parent to child

Question 5

Which expression is most appropriate for displaying an error only when an error message exists?

A. {errorMessage && <p role="alert">{errorMessage}</p>}

B. {errorMessage = <p>{errorMessage}</p>}

C. {errorMessage.map(message => <p>{message}</p>)}

D. {errorMessage++ && <p>{errorMessage}</p>}

Correct Answer: A. {errorMessage && <p role="alert">{errorMessage}</p>}

Question 6

Why is onClick={handleSave()} usually incorrect for a button?

A. It prevents JSX from being compiled.

B. It calls the function during rendering rather than on the click.

C. It makes the button inaccessible by definition.

D. It converts the handler into a list key.

Correct Answer: B. It calls the function during rendering rather than on the click.

Question 7

Which situation most clearly justifies an effect?

A. Calculating a total from an array during rendering

B. Selecting a label based on a prop

C. Synchronizing the document title with a prop

D. Choosing between two JSX branches

Correct Answer: C. Synchronizing the document title with a prop

Question 8

What does composition allow a component to do?

A. Receive nested or named content from its caller

B. Mutate its parent’s state directly

C. Avoid returning JSX

D. Replace all event handlers with CSS

Correct Answer: A. Receive nested or named content from its caller

Question 9

What is a controlled input?

A. An input whose value is managed through React state or props

B. An input that cannot receive keyboard focus

C. An input whose value is stored only in a CSS rule

D. An input that automatically validates every server response

Correct Answer: A. An input whose value is managed through React state or props

Question 10

Which practice is most appropriate for external user-provided HTML?

A. Insert it directly with unsafe HTML APIs.

B. Assume JSX makes all HTML safe.

C. Sanitize it rigorously before using an unsafe HTML insertion mechanism.

D. Place it in a random key.

Correct Answer: C. Sanitize it rigorously before using an unsafe HTML insertion mechanism.

Web Development using JavaScript/React
Chapter 6 of 14