Component-Based Frontend FrameworksIntroductionModern frontend applications are rarely built as one long HTML document with a small amount of scripting. They are assembled from reusable, independently understandable units called components. A navigation bar, product card, search field, modal dialog, or checkout form can each be designed as a component and then combined into a larger interface. Component-based frameworks provide conventions and tools for this approach. React, Vue, Angular, Svelte, and similar technologies help developers describe interfaces, manage changing data, respond to user events, and organize larger applications. However, a framework does not replace the web platform. HTML semantics, CSS, JavaScript, HTTP, accessibility standards, and browser behavior remain the foundation on which frameworks operate. This chapter uses React-style examples because its component model is widely used and its concepts transfer well to other frameworks. The examples use modern JavaScript and function components. The purpose is not to memorize one library’s syntax, but to understand reusable interface design. Learning ObjectivesBy the end of this chapter, you should be able to: - Explain component-based architecture and its advantages.
- Create reusable components using templates or JSX.
- Distinguish props, state, events, and derived values.
- Explain rendering and lifecycle concepts.
- Apply composition rather than unnecessary inheritance.
- Preserve HTML semantics, accessibility, and web standards in framework applications.
- Compare frameworks using technical and organizational criteria.
- Identify common component design errors and improve a small frontend application.
1. From Documents to ComponentsTraditional web development begins with documents. HTML describes the structure and meaning of a page, CSS describes presentation, and JavaScript adds behavior. This separation remains valuable. A framework adds a method for organizing the JavaScript and the interface structure as the application grows. A component is a unit that generally includes: - a visual or semantic structure;
- behavior, such as responding to clicks or input;
- data requirements;
- styling or styling assumptions; and
- a public interface through which other components use it.
For example, a ProductCard may accept a product object and an event handler, display the product information, and provide an “Add to basket” button. It should not need to know how the entire shopping system stores orders. A useful conceptual model is: Application
├── Header
│ ├── Logo
│ └── Navigation
├── ProductPage
│ ├── FilterPanel
│ └── ProductList
│ └── ProductCard × many
└── Footer The hierarchy is important because it makes ownership and responsibility visible. A component should have a focused purpose, but “small” does not automatically mean “good.” Excessive fragmentation can make an application difficult to follow. Components and web standardsA component is not inherently accessible or semantic. A framework can generate a div-based interface that is visually attractive but difficult for keyboard users and screen readers. Developers must therefore select appropriate HTML elements: - use
button for an action; - use
a for navigation; - use
label with form controls; - use headings in a meaningful hierarchy;
- use lists for lists;
- use native dialog or carefully implemented dialog behavior for modal content; and
- use ARIA only when native HTML cannot express the required meaning.
Framework code should be viewed as another way of producing a web document, not as an alternative to the web platform. 2. Templates and Declarative RenderingA template describes what the interface should look like for a particular set of data. In React, JSX permits HTML-like syntax inside JavaScript: function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}JSX is transformed by a build tool into JavaScript expressions. It is not literally HTML, although it resembles HTML. Attribute names sometimes differ: className is commonly used instead of class, and event properties use forms such as onClick. A declarative approach states the desired result: function Status({ loading, error, items }) {
if (loading) return <p role="status">Loading products…</p>;
if (error) return <p role="alert">Unable to load products.</p>;
if (items.length === 0) return <p>No products found.</p>;
return <ProductList items={items} />;
}The developer describes the interface for each condition rather than manually finding and modifying several DOM nodes. The framework compares the new description with the previous one and updates the browser DOM as needed. The exact reconciliation strategy differs by framework, but the conceptual model is similar. Templates should remain readable. Complex calculations, network requests, and large blocks of conditional logic generally belong outside the markup or in focused components. A template should communicate structure and intent. 3. Props: A Component’s External InterfaceProps (properties) are values supplied by a parent component to a child component. They are the component equivalent of function arguments. function ProductCard({ product, onAdd }) {
return (
<article>
<h2>{product.name}</h2>
<p>£{product.price.toFixed(2)}</p>
<button type="button" onClick={() => onAdd(product.id)}>
Add to basket
</button>
</article>
);
}A parent can use the component as follows: <ProductCard product={product} onAdd={handleAdd} />Props should be treated as read-only by the receiving component. If a child needs to request a change, it communicates through a callback or an event-like interface supplied by the parent. This produces a predictable direction of data flow: Parent state → props → child rendering
Child interaction → callback → parent state update Good props are purposeful and stable. Passing a large application object to every component increases coupling. A component should receive the information it needs, such as name, price, and imageUrl, or a clearly defined domain object. Prop validation and contractsA component contract should specify: - required and optional values;
- expected data types and shapes;
- allowed values, such as a button’s visual variant;
- callback behavior; and
- accessibility responsibilities.
TypeScript, PropTypes, schema validators, or documented JavaScript conventions can support these contracts. Validation catches errors near their source and makes components easier for a team to use. 4. State and EventsState is data that can change during a component’s lifetime and whose change may require a new render. Typical state includes whether a menu is open, the current form input, selected filters, and the result of a request. A React-style example is: import { useState } from 'react';
function QuantityControl() {
const [quantity, setQuantity] = useState(1);
function increase() {
setQuantity(current => current + 1);
}
return (
<div>
<button type="button" onClick={() => setQuantity(q => Math.max(1, q - 1))}>
−
</button>
<output aria-live="polite">{quantity}</output>
<button type="button" onClick={increase}>+</button>
</div>
);
}The initial state is 1. Calling the setter schedules an update; it does not directly mutate the existing state value. Functional updates, such as current => current + 1, are useful when the next value depends on the previous value. State ownershipState should normally be owned by the lowest common ancestor that needs to coordinate it. If only a search field uses a value, keep it local. If a product list and a result summary both require the selected category, move the category state to their common parent and pass it down. This process is often called lifting state up. It avoids two components having conflicting copies of the same truth. Controlled and uncontrolled form elementsIn a controlled input, the framework state is the source of truth: function SearchBox({ value, onChange }) {
return (
<label>
Search
<input
type="search"
value={value}
onChange={event => onChange(event.target.value)}
/>
</label>
);
}Controlled fields support validation and coordinated interfaces. Uncontrolled fields allow the browser to maintain the current value, with a reference used when the value is needed. Neither approach is universally superior; the choice depends on complexity, performance, and the need for immediate validation. 5. Rendering, Identity, and LifecycleA component may render many times. Rendering means calculating the interface description from current props and state. It should be free of unintended side effects. A render should not send an HTTP request, subscribe repeatedly to a service, or modify unrelated DOM nodes. A simplified lifecycle model contains three stages: - Mounting: the component is first inserted into the interface.
- Updating: props or state change and the component renders again.
- Unmounting: the component is removed.
Frameworks expose lifecycle mechanisms differently. React’s useEffect is commonly used to synchronize with external systems: import { useEffect, useState } from 'react';
function UserName({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(response => {
if (!response.ok) throw new Error('Request failed');
return response.json();
})
.then(setUser)
.catch(error => {
if (error.name !== 'AbortError') console.error(error);
});
return () => controller.abort();
}, [userId]);
return <p>{user ? user.name : 'Loading…'}</p>;
}The effect runs when userId changes. Its cleanup aborts the previous request when the component is removed or before a new effect is established. This prevents obsolete requests from updating the interface. Effects are not a general-purpose place for derived values. If a value can be calculated from existing props and state during rendering, calculating it directly is usually simpler: const fullName = `${firstName} ${lastName}`;Keys and identityWhen rendering a collection, each item needs a stable key: {products.map(product => (
<ProductCard key={product.id} product={product} onAdd={onAdd} />
))}A key helps the framework identify which conceptual item remains the same across updates. A database identifier is generally better than an array index, especially when items can be inserted, deleted, or reordered. Incorrect keys can cause input values or component state to appear attached to the wrong item. 6. Composition and ReuseComposition means building larger components by placing smaller components inside them or passing content into them. It is usually more flexible than inheritance for user interfaces. function Panel({ title, children }) {
return (
<section className="panel">
<h2>{title}</h2>
<div className="panel__body">{children}</div>
</section>
);
}
function AccountPage() {
return (
<Panel title="Account settings">
<ProfileForm />
</Panel>
);
}The children prop allows Panel to provide layout and semantics without knowing the details of its content. Other forms of composition include render props, slots in Vue, content projection in Angular, and higher-order abstractions. Their syntax differs, but the design principle is shared: separate structure from content and behavior. Reusable component designA robust reusable component usually has: - a narrow and understandable responsibility;
- explicit inputs and outputs;
- sensible defaults;
- no dependence on a specific page or global variable unless intentional;
- accessible keyboard and focus behavior;
- styling that does not unexpectedly leak into unrelated components; and
- tests for important states and interactions.
Avoid making every component configurable before a real need exists. Generality has a cost: more props, more conditions, and a larger mental model. 7. Data Flow and Shared StateLocal state is preferable when possible. Shared state may be needed for authentication, a shopping basket, theme preferences, or data used by distant parts of a page. Common solutions include: - passing props through a short hierarchy;
- context or dependency-injection mechanisms;
- a dedicated state store; and
- URL state for values that should be shareable, such as filters and page numbers.
A state store can improve coordination but can also hide relationships between data and its consumers. Before introducing one, ask whether the problem is genuinely shared state or merely inconvenient component structure. Server data also differs from interface state. A cached list of products has loading, error, freshness, and invalidation concerns. A local isMenuOpen value does not. Treating both as identical often produces unnecessary complexity. 8. Framework Selection CriteriaFramework selection should be based on project requirements rather than fashion. Important criteria include: | Criterion | Questions to ask |
|---|
| Rendering model | Is client-side, server-side, static, or hybrid rendering required? | | Team capability | What languages and conventions does the team already understand? | | Ecosystem | Are routing, forms, testing, and accessibility tools mature? | | Performance | What are the bundle, startup, interaction, and rendering requirements? | | Accessibility | Does the framework and its component ecosystem support semantic, keyboard-accessible interfaces? | | Maintainability | Are upgrades, documentation, and debugging practices reliable? | | Hosting and deployment | Does the framework fit the target infrastructure? | | Community and governance | Is the project actively maintained with transparent decision-making? | | Security | How are dependencies, rendering, and user-generated content handled? |
React is a library-oriented ecosystem with considerable choice. Angular provides a more integrated structure, TypeScript emphasis, dependency injection, and established conventions. Vue offers a progressive approach with single-file components and a gentle learning path. Svelte shifts more work to a compilation stage. These are broad tendencies, not absolute limitations; each ecosystem changes over time. A professional decision should evaluate a small proof of concept, measure realistic performance, review accessibility, and estimate long-term maintenance. A framework’s popularity alone is not a sufficient selection criterion. 9. Worked Application: A Filterable Product CatalogueConsider a catalogue that displays products, supports category filtering, and allows an item to be added to a basket. RequirementsThe interface must: - display products received from a parent or service;
- provide a category selector;
- show the number of matching products;
- preserve semantic form controls; and
- expose an add-to-basket action.
Design analysisThe product data is not owned by an individual card. The catalogue owns the selected category because both the selector and the list depend on it. The matching list is derived from products and category, so it need not be stored separately. function Catalogue({ products, onAddToBasket }) {
const [category, setCategory] = useState('all');
const categories = [
'all',
...new Set(products.map(product => product.category))
];
const visibleProducts = category === 'all'
? products
: products.filter(product => product.category === category);
return (
<main>
<h1>Catalogue</h1>
<label htmlFor="category">Category</label>
<select
id="category"
value={category}
onChange={event => setCategory(event.target.value)}
>
{categories.map(item => (
<option key={item} value={item}>
{item === 'all' ? 'All products' : item}
</option>
))}
</select>
<p aria-live="polite">
{visibleProducts.length} product(s) shown
</p>
{visibleProducts.length === 0 ? (
<p>No products match this category.</p>
) : (
<ul>
{visibleProducts.map(product => (
<li key={product.id}>
<ProductCard
product={product}
onAdd={onAddToBasket}
/>
</li>
))}
</ul>
)}
</main>
);
}This example illustrates several principles: state is local to the catalogue, derived data is calculated rather than duplicated, keys use stable product identifiers, and native label, select, main, ul, and li elements communicate meaning. In production, currency formatting should use Intl.NumberFormat, product images should have useful alternative text, and loading and error states should be designed explicitly. 10. Illustrative Case: Rebuilding a Training PortalBackgroundA fictional training provider has a portal containing course cards, a navigation drawer, progress indicators, and assessment forms. Its first version consists of duplicated HTML and page-specific scripts. A change to the course card must be repeated in six places. ProblemThe portal has inconsistent buttons, inaccessible custom controls, duplicated filtering logic, and state that is difficult to trace. Students also report that the navigation drawer loses focus when opened with a keyboard. AnalysisThe team identifies reusable units: CourseCard, CourseGrid, FilterBar, NavigationDrawer, ProgressBar, and AssessmentForm. It distinguishes local state, such as whether the drawer is open, from shared state, such as the authenticated learner and selected course. It also audits the current HTML for heading structure, labels, focus order, and keyboard operation. Approach and solutionThe team creates a design system with documented button, input, card, and alert components. Each component has explicit props and examples for normal, loading, empty, error, and disabled states. The drawer uses a real button to open and close, moves focus into the drawer when appropriate, returns focus to the opening control on close, and supports the Escape key. The course grid receives data and an event callback rather than importing the application store directly. Automated tests check rendering and key interactions; manual keyboard and screen-reader testing supplements them. The team introduces a framework incrementally rather than rewriting every page at once. OutcomeThe fictional team reduces duplication and makes visual changes more consistent. More importantly, the component contract and accessibility tests prevent regressions. The case also reveals that framework adoption alone did not solve the problems. Improvement resulted from component boundaries, standards-based markup, testing, and a deliberate migration plan. Learning points- Reuse is valuable when responsibility and interfaces are clear.
- Accessibility must be designed into components, not added only at page level.
- Framework migration is an organizational and technical change.
- Shared state should be introduced only when its scope is understood.
- Testing component states is as important as testing the successful path.
11. Common Problems and LimitationsOverly large componentsA component that fetches data, manages navigation, validates forms, calculates business rules, and renders a large page is difficult to test. Separate responsibilities where the separation improves comprehension. Prop drillingPassing values through several layers can indicate that state belongs elsewhere, but it can also be a clear and explicit design. Do not introduce global state merely to avoid a few props. Incorrect effect usageEffects that update state derived from other state can create loops or unnecessary renders. Effects should primarily synchronize with external systems such as network requests, timers, browser APIs, or subscriptions. Unstable keysIndex keys can produce confusing behavior when a list changes order. Use stable identifiers tied to the item’s identity. Framework abstraction leakageA component library may provide a convenient Button that renders a div or hides native form behavior. Inspect the generated interface and test it with a keyboard and assistive technology. Performance misconceptionsA framework does not automatically make an application fast. Performance depends on network delivery, JavaScript size, rendering work, images, data access, and interaction design. Measure using realistic devices and representative data. Memoization should be applied when measurement indicates a meaningful problem, not as a default decoration. 12. Security, Ethics, and GovernanceFrameworks can reduce some classes of error but do not make applications secure. Output escaping helps against some injection attacks, but dangerous HTML insertion, unsafe URL handling, insecure dependencies, and poor server authorization remain serious risks. Authorization must be enforced on the server, not only by hiding a client-side button. Personal data should be collected and displayed only for legitimate purposes. Components that show profiles, progress, or assessment results should apply appropriate access controls and minimize exposed information. Analytics components should respect consent and organizational policy where required. Open-source dependencies require governance: record versions, review security advisories, maintain lockfiles, and remove unused packages. Teams should also consider the environmental cost of excessive client-side JavaScript, including network transfer and device energy use. 13. Contemporary DevelopmentsCurrent frontend practice includes server-side rendering, static generation, streaming, partial hydration, web components, design-system tooling, and increasingly sophisticated build systems. These approaches can improve initial delivery or search visibility, but they introduce trade-offs involving caching, data loading, deployment, and debugging. Web Components provide browser-native custom elements, shadow DOM, and templates. They can be used independently of a major framework and are useful for cross-framework design systems. Framework components may offer richer application conventions, while Web Components provide a standards-based interoperability boundary. AI-assisted coding tools can generate component scaffolds, tests, and refactoring suggestions. Generated code must be reviewed for accessibility, security, licensing, correctness, and unnecessary complexity. An assistant may produce plausible JSX that uses incorrect semantics or leaks sensitive data. Professional responsibility remains with the developer and organization. Best Practices Checklist- Define a component’s responsibility before writing its API.
- Use semantic HTML elements inside framework templates.
- Keep data flow understandable and generally directional.
- Treat props as read-only and update state through supported mechanisms.
- Store the minimum necessary state; derive the rest.
- Use stable keys for dynamic collections.
- Clean up subscriptions, timers, and requests.
- Design loading, error, empty, and disabled states.
- Test keyboard access, focus behavior, and screen-reader meaning.
- Measure performance before optimizing.
- Select frameworks according to requirements, team capability, and maintenance costs.
- Keep dependencies and generated code under review.
Chapter SummaryComponent-based frameworks organize frontend applications as reusable units containing structure, behavior, and clearly defined interfaces. Templates express the desired interface declaratively, while props provide inputs and state represents changing local data. Events and callbacks allow children to request changes without directly controlling parent state. Rendering can be understood through mounting, updating, and unmounting. Effects are appropriate for synchronization with external systems, and cleanup prevents obsolete work from continuing. Composition allows components to be combined flexibly, while stable keys preserve item identity in dynamic lists. Shared state should be introduced deliberately, and derived values should not be stored unnecessarily. Framework choice requires consideration of rendering needs, ecosystem maturity, accessibility, performance, team skills, security, governance, and long-term maintenance. Most importantly, framework code remains web code: semantic HTML, CSS, JavaScript, accessibility, security, and browser standards continue to govern the quality of the result. Key TermsComponent: A reusable interface unit containing structure, behavior, and a defined contract. Props: Read-only values passed from a parent component to a child. State: Changeable data whose updates may cause a component to render again. Template: A declarative description of interface structure. JSX: JavaScript syntax that resembles HTML and is transformed into JavaScript. Declarative rendering: Describing the desired result rather than manually issuing DOM operations. Event handler: A function executed in response to a user or browser event. Lifecycle: The stages through which a component is mounted, updated, and unmounted. Effect: Logic that synchronizes a component with an external system. Cleanup: Work that stops or reverses an effect when it is no longer needed. Composition: Combining components to create a larger interface. Controlled input: A form control whose value is managed by application state. Derived value: Data calculated from existing props or state rather than stored independently. Key: A stable identifier that helps a framework preserve collection-item identity. Prop drilling: Passing data through intermediate components that do not otherwise use it. Semantic HTML: HTML whose elements communicate the meaning and structure of content. Accessibility: Designing interfaces usable by people with diverse abilities and assistive technologies. Hydration: Connecting client-side behavior to markup generated before client execution. Design system: A maintained collection of reusable components, styles, rules, and documentation. Web Component: A browser-standard custom element technology using features such as custom elements and shadow DOM. Review QuestionsShort Answer Questions- What is a component, and what responsibilities can it contain?
- How do props differ from state?
- Why should a dynamic list use stable keys?
- When is an effect appropriate in a component?
- Why must framework applications continue to use semantic HTML?
Long Answer Questions- Explain the relationship among templates, rendering, props, state, and events in a component-based application.
- Compare composition with inheritance as a strategy for building user-interface components.
- Describe the mounting, updating, and unmounting stages and explain the role of cleanup.
- Discuss the major criteria that should guide selection among contemporary frontend frameworks.
- Explain how accessibility, security, and governance should influence component-library design.
Analytical / Application Questions- A product list uses array indexes as keys and allows products to be reordered. Analyze the likely problems and propose a better design.
- A form component receives user data, validates it, submits it, and also controls a global navigation drawer. Evaluate its boundaries and redesign its responsibilities.
- Design the component hierarchy and state ownership for a course catalogue with category filters, loading and error states, a progress indicator, and an enrollment action. Justify which values are props, local state, derived data, or shared state.
Multiple Choice Questions with AnswersQuestion 1Which statement best describes props? A. They are read-only values supplied to a component B. They are browser cookies stored by a component C. They are always global variables D. They are CSS declarations Correct Answer: A. They are read-only values supplied to a component Question 2Where should state normally be placed when two sibling components need the same value? A. Inside an unrelated leaf component B. In their lowest common ancestor C. Only in the browser URL D. Inside the CSS file Correct Answer: B. In their lowest common ancestor Question 3What is the main purpose of a stable key in a rendered collection? A. To encrypt each item B. To determine the item’s font size C. To help the framework preserve item identity D. To replace HTML semantics Correct Answer: C. To help the framework preserve item identity Question 4Which task is most appropriate for an effect? A. Calculating a full name from two strings B. Synchronizing with a network request C. Choosing a heading element D. Formatting a static label Correct Answer: B. Synchronizing with a network request Question 5What does composition mean in component design? A. Combining smaller components to form a larger interface B. Copying all application state into every component C. Replacing HTML with CSS D. Using inheritance for every visual variation Correct Answer: A. Combining smaller components to form a larger interface Question 6Which element is generally most appropriate for a user-triggered action? A. A div with no role B. A span containing text C. A button D. An arbitrary heading Correct Answer: C. A button Question 7Why is derived data often not stored as separate state? A. It cannot be displayed B. It can be calculated from existing props or state C. Frameworks cannot store strings D. It must always come from a server Correct Answer: B. It can be calculated from existing props or state Question 8Which is an important framework-selection criterion? A. Whether its name has exactly five letters B. Whether it eliminates the need for HTML C. Whether it fits project requirements and maintenance capabilities D. Whether it prevents all security vulnerabilities Correct Answer: C. Whether it fits project requirements and maintenance capabilities Question 9What should cleanup commonly do? A. Start duplicate subscriptions B. Stop timers, subscriptions, or obsolete requests C. Delete all component props D. Disable keyboard access Correct Answer: B. Stop timers, subscriptions, or obsolete requests Question 10Which statement about AI-generated frontend code is most appropriate? A. It never requires review B. It is automatically accessible C. It should be reviewed for correctness, security, accessibility, and complexity D. It removes developer responsibility Correct Answer: C. It should be reviewed for correctness, security, accessibility, and complexity
|