Chapter 6
State Management and Reactive Programming
Diploma in Full Stack Programming
Frontend Development using React.js or Angular
•
Diploma
State Management and Reactive Programming
Introduction
Modern frontend applications rarely display static information. They coordinate user input, navigation, asynchronous requests, authentication, cached data, validation, notifications, and real-time updates. State is the information that determines what the interface displays and how it behaves. State management is the disciplined process of storing, changing, deriving, sharing, and synchronising that information.
React and Angular provide different but related approaches. React commonly uses hooks, component properties, Context, reducers, and external stores. Angular traditionally uses services and RxJS, while modern Angular also provides signals. Both ecosystems encourage unidirectional data flow, predictable updates, and separation between view logic and application data.
A poor state design can produce stale displays, unnecessary network requests, race conditions, difficult testing, and tightly coupled components. A suitable design makes ownership explicit, limits mutation, and distinguishes short-lived UI state from shared or server state.
Learning Objectives
By the end of this chapter, a learner should be able to:
distinguish local, shared, derived, and server state;
explain immutability and unidirectional data flow;
manage React state with hooks, Context, and reducers;
manage Angular state with services, signals, and RxJS;
explain observable streams, operators, subscriptions, and cancellation;
choose between built-in mechanisms and external state libraries;
design a practical state solution for an application; and
identify performance, testing, accessibility, security, and governance concerns.
1. Understanding State
State may be classified by ownership, lifetime, and source.
State category
Meaning
Typical examples
Suitable first choice
Local UI state
Used by one component or a small visual region
Modal visibility, input text, selected tab
React useState; Angular signal
Shared client state
Used by several related components
Shopping cart, theme, user preferences
Context/service/store
Derived state
Computed from other state
Total price, filtered list, button enabled status
Selector or computed value
Server state
Retrieved from or persisted to a backend
Products, orders, profiles
Query/cache library or service
URL state
Encoded in the address and shareable
Search term, page number, route identifier
Router state
Session or identity state
Governs access and continuity
Token metadata, current user, permissions
Auth service with secure storage strategy
A useful design principle is state locality: keep state as close as possible to the components that need it. State should be promoted to a shared owner only when communication between separate consumers justifies doing so. Excessive global state makes dependencies invisible and increases the number of possible update paths.
Source of truth and derived values
A source of truth is the authoritative representation of a fact. If a product quantity is stored in two components, the values can diverge. It is generally safer to store the quantity once and derive totals, labels, and validation messages from it. Derived state should not normally be copied into another mutable variable because copying introduces synchronisation problems.
For example, if cart items have price and quantity, the total is derived as:
Total = ∑ᵢ₌₁ⁿ priceᵢ × quantityᵢ
Here, n is the number of cart lines; priceᵢ is the unit price of line i in the chosen currency; and quantityᵢ is its quantity. The expression assumes prices and quantities are valid and that all lines use the same currency. In production systems, monetary calculations should avoid binary floating-point errors, commonly by storing the smallest currency unit as an integer or using a decimal library.
2. Immutability and Unidirectional Data Flow
Immutability means treating an existing state value as unchangeable and creating a new value when the state changes. JavaScript objects and arrays are mutable by default, so developers must avoid accidental in-place operations such as items.push(newItem) when a framework relies on reference changes to detect updates.
An immutable update creates a new array or object:
Immutability does not mean that every value is deeply frozen at runtime. It is a programming discipline that provides predictable change detection, easier debugging, and safer time-based reasoning. Libraries such as Immer can provide a draft-style syntax while producing immutable results.
Unidirectional data flow means data moves through a controlled direction: a state owner supplies data to a view, the user generates an event, and an update function or action changes the state. The updated state then produces a new view.
Unidirectional data flow from state to view and event back to update
Figure: Unidirectional data flow from state to view and event back to update
Figure: A controlled cycle of state, view, event, and update.
This model is not a prohibition on all two-way binding. Rather, it makes the authoritative update path explicit and reduces hidden side effects.
3. React State Management
useState
useState is appropriate for independent local values. Its setter schedules a re-render, and updates based on previous state should use the functional form:
function QuantityControl() {
const [quantity, setQuantity] = useState(1);
function increase() {
setQuantity(value => value + 1);
}
return <button onClick={increase}>{quantity}</button>;
}
The functional form is important when several updates may be batched or when the next value depends on the previous value. State initialisation can also be lazy when computation is expensive: useState(() => buildInitialValue()).
useReducer
A reducer is a pure function that receives current state and an action and returns next state:
Reducers are useful when transitions are numerous, related, or business-oriented. Actions document what happened, while the reducer centralises the transition rules. A reducer should not perform HTTP requests, access the DOM, read random time, or mutate its arguments. Side effects belong in event handlers, effects, or service layers.
Effects and asynchronous work
useEffect synchronises a component with an external system such as a browser API, subscription, or network request. It should not be used merely to calculate a value that can be calculated during rendering. Its dependency list states which values cause the synchronisation to be re-established. Cleanup is essential for removing listeners, cancelling requests where supported, and unsubscribing.
A request should represent explicit loading, success, and failure states rather than assuming that data is immediately available:
A robust application also handles stale responses. For example, an AbortController can cancel a previous request when a new search term is submitted. Debouncing user input can reduce unnecessary requests, but it is not a substitute for server-side validation.
Context
Context supplies a value to descendants without passing it through every intermediate component. It is useful for relatively stable cross-cutting values such as locale, theme, or an authenticated user. Context is not automatically a complete global store. A provider value that changes frequently can re-render many consumers, so split contexts, memoised values, selectors, or a dedicated store may be preferable.
A common pattern combines Context with useReducer: the provider owns state and dispatch, while consumers receive them. This provides a small application-level store without adding a dependency. It remains important to define action types, validation, persistence, and testing boundaries.
4. Angular State Management
Services as state owners
Angular services can be provided at the application root or at a narrower injector scope. A service is a suitable owner for data shared by components, especially when it also coordinates HTTP access, caching, or business rules. Components should generally request state and invoke meaningful service methods rather than manipulate another component’s internals.
Signals
A signal stores a value and notifies consumers when that value changes. A writable signal can be updated with set or update, while a computed signal derives a read-only value:
Signals encourage fine-grained reactive dependencies. The example preserves array immutability even though the signal itself is writable. A computed value should be deterministic and free from side effects. Effects are appropriate for synchronisation with external systems, not as a general replacement for business logic.
RxJS in Angular
RxJS represents asynchronous or time-varying values as Observables. An Observable may emit zero, one, or many values over time, then complete or error. It is usually lazy: creating a pipeline does not necessarily start work until it is subscribed to.
Perform a carefully controlled observation side effect
debounceTime
Wait for a quiet period before emitting
distinctUntilChanged
Ignore consecutive duplicate values
switchMap
Cancel or disregard the previous inner stream when a new value arrives
mergeMap
Run inner operations concurrently
concatMap
Queue inner operations in order
exhaustMap
Ignore new triggers while one operation is active
catchError
Recover from or transform an error
shareReplay
Share and replay values, with cache-lifetime considerations
The choice among flattening operators expresses business intent. switchMap is suitable for type-ahead search because only the latest term matters. concatMap suits ordered saves. exhaustMap can prevent repeated form submissions while one is processing. mergeMap is suitable when requests may safely complete concurrently.
Subscriptions must be managed. Angular’s template async pipe and modern destruction-aware utilities reduce manual cleanup. In any framework, an unbounded subscription can retain component references and cause memory leaks.
Signals and Observables together
Signals are often convenient for synchronous application state and template consumption; Observables are strong for event streams, HTTP pipelines, WebSocket data, and cancellation. Conversion utilities can connect them, but developers should define which representation is authoritative and avoid repeated conversions that obscure ownership.
5. Server State and Caching
Server state differs from local state because it has remote ownership, latency, failure, permissions, and possible changes by other users. A server-state solution commonly tracks:
data and freshness;
loading and error status;
request parameters;
cache keys and invalidation;
retries and cancellation; and
optimistic or pessimistic updates.
A query/cache library can provide deduplication, background refetching, pagination, mutation tracking, and invalidation. A global client store should not automatically become a substitute for such a library. For example, copying every HTTP response into a hand-built store may duplicate caching logic and create stale records.
An optimistic update changes the interface before server confirmation. It improves perceived responsiveness but requires a rollback strategy if the request fails. A pessimistic update waits for confirmation and is safer when correctness is more important than immediate feedback. Both approaches require clear treatment of concurrent edits and authorization failures.
6. External State Libraries and Selection Criteria
External libraries are justified when application complexity exceeds the clarity offered by built-in tools. Examples in the React ecosystem include Redux Toolkit, Zustand, and Jotai; Angular applications may use NgRx or other signal-oriented stores. Specific APIs evolve, so teams should consult the current documentation and follow the project’s supported version.
Select a library by evaluating:
State scope: Is the problem local, feature-wide, or application-wide?
Update complexity: Are transitions numerous and business-critical?
Asynchronous needs: Are caching, cancellation, retries, or synchronisation required?
Debugging: Are action histories, tracing, or time-travel inspection valuable?
Performance: Does the library support selective subscriptions?
Team capability: Can the team test and maintain the abstraction?
Operational cost: Does it add bundle size, conventions, and upgrade work?
A store should expose a small public API, protect invariants, and avoid allowing arbitrary components to modify internal structures.
7. Worked Application: A Course Enrolment Portal
Background and problem
Consider an illustrative diploma project: a course-enrolment portal displays available courses, allows a learner to select courses, calculates total credits, and submits the enrolment. Course data comes from an API; the draft selection is local to the current session; the selected learner identity comes from authentication state.
Analysis
The course catalogue is server state. The selected course identifiers are shared feature state because the catalogue, summary panel, and submission form need them. Total credits are derived state. The open filter panel is local UI state. The route’s search term is URL state because it should be bookmarkable.
Approach
A React design might use a query library for the catalogue, a feature Context plus reducer for selected courses, and a memoised calculation for total credits. The reducer could support courseAdded, courseRemoved, and selectionCleared. The submission handler would validate prerequisites, send the selected identifiers, and invalidate or refresh the enrolment query after success.
An Angular design might use a service containing a selected-course signal and a computed total. The catalogue would be an Observable or signal-backed resource from an API service. A search input could produce an Observable pipeline using debounceTime, distinctUntilChanged, and switchMap. The submit method would disable repeated submission, handle server errors, and restore the enabled state in all completion paths.
Outcome
This design prevents the summary from maintaining a second total, prevents stale search responses from replacing newer results, and makes the submission boundary explicit. Tests can verify reducer transitions or service methods without rendering the entire application.
Learning points
The central lesson is to classify state before selecting technology. A reducer is not automatically better than a signal, and a global store is not automatically more professional. The correct solution is the smallest architecture that provides reliable ownership, predictable transitions, and appropriate server synchronisation.
8. Testing, Performance, Security, and Accessibility
Pure reducers, selectors, computed values, and mapping functions are straightforward unit-test targets. Test success, loading, empty, error, cancellation, retry, and stale-response scenarios. Component tests should verify observable behaviour rather than implementation details.
Performance improves when state is local, consumers subscribe selectively, expensive derived values are memoised appropriately, and large lists are virtualised where necessary. Memoisation is not automatically beneficial: it has memory and comparison costs and should be guided by evidence.
State can contain personal, financial, or access-related information. Do not place secrets or long-lived sensitive tokens in ordinary client storage without a carefully assessed threat model. Enforce authorization on the server; hidden buttons are not access control. Minimise persisted data, define retention rules, and avoid logging personal information in Redux-style action histories or browser consoles.
State transitions also affect accessibility. Loading and error states should be announced appropriately, focus should be managed after significant navigation or dialog changes, and disabled controls should not silently remove information about why an action is unavailable. Offline and retry behaviour should communicate status clearly.
9. Common Failure Modes and Best Practices
Common failures include mutating arrays in place, storing duplicated derived data, placing every value in global state, subscribing without cleanup, ignoring request races, using useEffect for ordinary calculations, and selecting RxJS operators without considering concurrency semantics.
Best practices are to:
define ownership and a single source of truth;
keep state as local as practical;
use immutable transitions;
represent loading, success, empty, and error states explicitly;
keep reducers and computed functions pure;
choose concurrency operators according to business meaning;
cancel or ignore stale asynchronous work;
separate server caching from client UI state;
expose narrow store and service APIs;
test transitions and failure paths; and
document persistence, privacy, and invalidation decisions.
Chapter Summary
State management concerns the ownership, transition, derivation, sharing, and synchronisation of application data. Local UI state should remain local where possible; shared state needs an explicit owner; derived values should be computed rather than duplicated; and server state requires caching, freshness, error, and concurrency strategies.
React offers useState, useReducer, useEffect, and Context, while Angular offers services, signals, and RxJS. Immutability and unidirectional flow make updates predictable. Observables model asynchronous streams, and RxJS flattening operators encode different concurrency policies. External libraries are valuable when complexity, caching, debugging, or selective subscriptions justify their cost. Effective solutions also address testing, performance, accessibility, security, privacy, and maintainability.
Key Terms
State: Information that determines an application’s current behaviour or display.
State management: The organisation of state storage, updates, derivation, sharing, and synchronisation.
Local state: State used by one component or visual region.
Shared state: State consumed by multiple components or features.
Server state: Data owned by and synchronised with a remote backend.
Derived state: A value calculated from other state.
Immutability: Treating an existing value as unchanged and producing a new value for an update.
Unidirectional data flow: A controlled flow from state to view and from events to state updates.
Reducer: A pure function that calculates next state from current state and an action.
Action: A description of an event or requested state transition.
Context: A React mechanism for supplying values to descendants without explicit prop passing.
Signal: A reactive value that tracks consumers and notifies them when it changes.
Observable: A stream abstraction that can emit values, errors, and completion over time.
Subscription: The act of listening to an Observable and activating or receiving its stream.
Operator: An RxJS function that transforms, filters, combines, or controls streams.
Cancellation: Stopping or disregarding asynchronous work that is no longer relevant.
Selector: A function that reads or derives a portion of store state.
Optimistic update: Updating the interface before server confirmation.
Cache invalidation: Marking stored server data as potentially stale and requiring refresh.
Review Questions
Short Answer Questions
Distinguish local state from server state and give one example of each.
Why should derived values generally not be stored as independent mutable state?
What problem does the functional form of a React state setter solve?
Define an Observable and a subscription.
What is the main purpose of a reducer?
Long Answer Questions
Explain immutability and unidirectional data flow, and discuss how they improve maintainability.
Compare React Context with an external state library.
Explain Angular signals and RxJS Observables, including situations in which each is appropriate.
Compare switchMap, concatMap, mergeMap, and exhaustMap using practical application examples.
Describe the design of a secure and testable server-state solution for a data-driven application.
Analytical / Application Questions
A search interface displays results from an API, but slow responses sometimes overwrite newer results. Diagnose the problem and propose a reactive solution.
A team stores cart items, cart totals, loading flags, and API responses in a single global store. Analyse the risks and redesign the state boundaries.
Design either a React or Angular state architecture for an enrolment form that supports validation, optimistic feedback, server errors, cancellation, and accessibility.
Multiple Choice Questions with Answers
Question 1
Which state category is most appropriate for a modal's open or closed status used by one component?
A. Local UI state
B. Server state
C. Global identity state
D. Cached route state
Correct Answer: A. Local UI state
Question 2
What is the principal benefit of an immutable state update?
A. It guarantees that network requests succeed
B. It makes change detection and reasoning about transitions more predictable
C. It removes the need for testing
D. It automatically encrypts application data
Correct Answer: B. It makes change detection and reasoning about transitions more predictable
Question 3
Which React mechanism is specifically designed to calculate next state from current state and an action?
A. Context provider
B. Effect
C. Reducer
D. Ref
Correct Answer: C. Reducer
Question 4
Which RxJS operator is generally most suitable for type-ahead search where only the latest request matters?
A. concatMap
B. switchMap
C. exhaustMap
D. mergeMap
Correct Answer: B. switchMap
Question 5
What is a computed Angular signal primarily used for?
A. Deriving a value from other reactive values
B. Opening a database connection
C. Replacing server authorization
D. Mutating component templates directly
Correct Answer: A. Deriving a value from other reactive values
Question 6
Why should a total price usually be treated as derived state?
A. It is always supplied by the browser
B. It can be calculated from authoritative prices and quantities
C. It cannot change after page load
D. It must be stored in two components
Correct Answer: B. It can be calculated from authoritative prices and quantities
Question 7
Which operator queues inner asynchronous operations in order?
A. concatMap
B. switchMap
C. filter
D. debounceTime
Correct Answer: A. concatMap
Question 8
What is a major risk of placing rapidly changing values in a single React Context?
A. Consumers may receive too many re-renders
B. The values become server state automatically
C. Reducers become mathematically invalid
D. HTTP requests are always cancelled
Correct Answer: A. Consumers may receive too many re-renders
Question 9
Which concern is especially characteristic of server state?
A. Remote ownership and freshness
B. CSS inheritance
C. Keyboard layout only
D. Static HTML semantics only
Correct Answer: A. Remote ownership and freshness
Question 10
What is a key security principle for client-side state?
A. A hidden button is sufficient authorization
B. All state should be persisted permanently
C. Server-side authorization must enforce access control
D. Action histories should contain all secrets
Correct Answer: C. Server-side authorization must enforce access control
Frontend Development using React.js or Angular
Chapter 6
of 14