Unit 5: Client-Server Communication and Routing
I. Orientation
A React application commonly acts as a client: it requests resources from a server, presents asynchronous results, and maps browser URLs to views. Reliable applications therefore separate remote data synchronization from local interface state and treat routing as part of application architecture.
- Request–response model: A client sends an HTTP request; a server returns a response containing a status code, headers, and usually a representation such as JSON.
- Asynchronous operation: Network requests do not finish immediately, so interfaces must represent loading, success, empty, and error outcomes.
- State separation: Client state is locally controlled, while server state is remotely owned and may become stale.
- Declarative routing: A route configuration maps URL patterns such as
/products/:idto React components. - Synchronization principle: Data-fetching tools coordinate caches with remote sources; routing tools coordinate rendered views with browser location.
II. HTTP and Request Clients — Communicating with Web Services
HTTP defines the communication contract, while Fetch and Axios provide JavaScript interfaces for executing that contract.
A. HTTP fundamentals and REST principles
HTTP is a stateless request–response protocol in which methods identify intended operations and status codes describe outcomes.
- Request structure: A request includes a method, URL, headers, and an optional body;
POST /api/usersmay carry JSON in its body. - Common methods:
GET: Retrieve a resource without changing it.POST: Create a resource or initiate processing.PUT: Replace a complete resource.PATCH: Partially update a resource.DELETE: Remove a resource.
- Status codes:
200 OKindicates success,201 Createdcreation,204 No Contentsuccess without a body,400a bad request,401missing authentication,403forbidden access,404absence, and500a server failure. - REST resources: Resource-oriented endpoints use nouns, such as
/api/books/7, rather than operation names such as/getBook. - REST constraints: Requests are stateless, resources have representations, and standard HTTP semantics support a uniform interface.
- Idempotency: Repeating
GET,PUT, orDELETEshould produce the same intended server state; repeatingPOSTmay create duplicates. - Headers:
Content-Type: application/jsondescribes the request body, whileAccept: application/jsonrequests a JSON response.
B. Fetch API and Axios usage patterns
Fetch is browser-native, whereas Axios is a library offering convenience features and consistent request configuration.
- Fetch API:
- Response handling: Fetch rejects primarily on network failure; status codes such as
404require an explicitresponse.okcheck. - JSON conversion:
response.json()asynchronously parses the response body.
- Response handling: Fetch rejects primarily on network failure; status codes such as
async function getUser(id, signal) {
const response = await fetch(`/api/users/${id}`, { signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}- Axios:
- Automatic behavior: Axios parses JSON and rejects promises for non-
2xxresponses by default. - Instances and interceptors:
axios.create()can define a base URL, while interceptors can attach authorization tokens or process errors.
- Automatic behavior: Axios parses JSON and rejects promises for non-
import axios from "axios";
const api = axios.create({ baseURL: "/api" });
const { data } = await api.get("/users/7");- Cancellation: Both modern Fetch and Axios can use
AbortController; cancellation prevents obsolete requests from unnecessarily completing. - Mutation payloads: Fetch requires
JSON.stringify(data)and a content-type header, whileaxios.post("/users", data)serializes ordinary objects automatically.
III. Asynchronous Rendering and Effects — Managing Request Lifecycles
Network-driven components must model every visible outcome and prevent obsolete side effects from changing current UI.
A. Handling loading, error, and empty states
Asynchronous interfaces should distinguish an unfinished request, a failed request, and a successful response containing no records.
- Loading state: Display a spinner, skeleton, or progress message while data is unavailable; preserve existing data during background refresh when possible.
- Error state: Show an actionable message and retry control rather than exposing only a console error.
- Empty state: A successful response such as
[]is not an error; display “No products found” and, where appropriate, a creation or filter-reset action. - Conditional order: Check loading and error before rendering data, then test collection length.
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage message={error.message} />;
if (products.length === 0) return <p>No products found.</p>;
return <ProductList products={products} />;- Accessibility: Use
aria-live="polite"for status changes and avoid loading indicators that provide no textual meaning.
B. Side effects for data fetching
Data fetching is a side effect because it communicates with a system outside React’s pure rendering process.
- Effect timing:
useEffectruns after a committed render; its dependency array determines when synchronization is repeated. - Dependency rule: If a request uses
userId, includeuserIdso changing/users/4to/users/5triggers another request. - Cleanup: Abort requests when the component unmounts or the dependency changes, preventing obsolete results from winning a race.
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(setUser)
.catch(e => {
if (e.name !== "AbortError") setError(e);
});
return () => controller.abort();
}, [userId]);- Event-driven writes: Submit
POSTorDELETErequests in event handlers such ashandleSubmit, not in an effect that watches button-related state.
C. Limitations of useEffect for data synchronization
Manual useEffect fetching handles simple cases but does not itself provide a complete server-data synchronization system.
- Boilerplate: Each component must separately manage data, loading, errors, cancellation, and retry behavior.
- Race conditions: Rapid dependency changes can allow an older request to overwrite newer data unless cancellation or request identity is implemented.
- No shared cache: Two components requesting
/api/users/7may issue duplicate requests and maintain inconsistent copies. - Missing policies: Refetch-on-focus, stale-data rules, request deduplication, pagination, and mutation invalidation require custom logic.
- Development behavior: React Strict Mode may run an extra development-only effect setup and cleanup cycle, exposing unsafe effects.
- Waterfalls: Parent fetching followed by child fetching delays rendering because requests begin sequentially rather than in parallel.
IV. State Ownership and React Query — Synchronizing Remote Data
Correct state ownership determines whether ordinary React state or a dedicated server-state cache should manage a value.
A. Distinction between client state and server state
Client state belongs to the interface, while server state is a cached snapshot of information owned by a remote service.
-
Client state:
- Examples: Modal visibility, selected tab, unsaved form input, and theme preference.
- Control: Updated synchronously through
useState,useReducer, context, or a client-state store.
-
Server state:
- Examples: User records, product inventories, account balances, and paginated comments.
- Characteristics: Asynchronous, shared across clients, potentially stale, and subject to external changes.
- Derived values: A filtered product list should usually be computed from products and the current filter, not stored as another independent copy.
- Boundary: Copying fetched data into local state can disconnect it from cache updates; local state is justified when creating an editable draft.
B. Introduction to server state management using React Query
TanStack Query, commonly called React Query, manages fetching, caching, retries, invalidation, and synchronization through declarative queries.
- Provider setup: A
QueryClientstores cache entries and is supplied throughQueryClientProvider. - Query identity:
queryKey: ["user", userId]uniquely identifies data and includes every parameter used by the request. - Query function:
queryFnreturns a promise that resolves with data or rejects with an error.
const query = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
enabled: Boolean(userId)
});
const { data, isPending, isError, error } = query;- Mutations:
useMutationhandles create, update, or delete operations; successful mutations commonly invalidate related queries. - Invalidation:
queryClient.invalidateQueries({ queryKey: ["users"] })marks matching cached data stale and may trigger a refetch.
C. Caching strategies and background synchronization concepts
A query cache balances immediate reuse against the need to refresh data that may have changed remotely.
- Freshness:
staleTimespecifies how long data remains fresh; a value of60_000treats it as fresh for 60 seconds. - Garbage collection:
gcTimecontrols how long unused cached data remains before removal. - Cache-first rendering: Previously fetched data can render immediately when revisiting a view, avoiding a blank loading screen.
- Background refetching: Stale data may remain visible while a new request updates it;
isFetchingcan indicate this quieter synchronization. - Automatic triggers: Policies can refetch when a component mounts, the window regains focus, or network connectivity returns.
- Deduplication: Concurrent components using the same query key can share one in-flight request.
- Invalidation strategy: Invalidate affected keys after mutations; avoid clearing the entire cache when only
["todos"]changed.
V. Client-Side Routing — Mapping URLs to React Views
Client-side routing changes rendered views without requesting a complete new HTML document for every navigation.
A. Client-side routing fundamentals
A client-side router observes browser location and selects the matching React element.
- URL as state:
/products/12?tab=reviewsrecords a view that can be bookmarked, refreshed, or shared. - History API: Routers use browser history to push or replace entries while avoiding full-page reloads.
- Server requirement: Production hosting must usually return the application entry document for unknown non-file paths such as
/products/12. - Route matching: Static, dynamic, and nested path segments determine which component tree renders.
B. Routing configuration setup
React Router configures route matching through a router provider or declarative route components.
- Installation: The web package is commonly imported from
react-router-dom. - Configuration:
createBrowserRouterdefines route objects, whileRouterProviderconnects them to the application.
const router = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/products", element: <Products /> },
{ path: "*", element: <NotFound /> }
]);
root.render(<RouterProvider router={router} />);- Fallback route:
*catches unmatched locations and renders a not-found view.
C. Navigation between application views
Router navigation preserves single-page application behavior and browser history.
- Declarative navigation:
<Link to="/products">Products</Link>creates accessible navigation without reloading the document. - Active links:
NavLinkcan style a link according to whether its destination matches the current location. - Programmatic navigation:
useNavigate()is appropriate after actions such as successful login or form submission. - History replacement:
navigate("/login", { replace: true })replaces the current entry, preventing an invalid page from remaining in Back history. - External destinations: Use a normal
<a href>for another website or downloadable resource.
D. Dynamic routes and route parameters
Dynamic segments allow one route pattern to represent many resource-specific pages.
- Pattern:
/products/:productIdmatches/products/42;productIdreceives the string"42". - Reading parameters:
useParams()returns matched values.
const { productId } = useParams();
const id = Number(productId);- Validation: Because parameters are strings and users can edit URLs, validate
id;Number.isInteger(id)can reject malformed identifiers. - Data identity: Include the parameter in a query key, such as
["product", productId], to separate cache entries.
E. Query parameter handling
Query parameters represent optional URL state such as filters, search terms, sorting, and pagination.
- Syntax: In
/products?category=books&page=2, the path is/products, whilecategoryandpageare query parameters. - React Router API:
useSearchParams()reads and updates values through aURLSearchParams-like interface. - Type conversion:
searchParams.get("page")returns a string ornull; convert and validate before numerical use. - Updates: Setting
{ category: "books", page: "2" }creates a shareable and history-aware filtered view. - Appropriate use: Query parameters suit optional presentation state; resource identity generally belongs in a path parameter.
F. Nested routes and layout routes
Nested routing mirrors hierarchical interfaces by rendering child routes inside a parent layout.
- Outlet: A parent component renders
<Outlet />where the selected child element should appear. - Shared layout: Navigation, sidebars, and footers can remain stable while child content changes.
- Index route: An index child renders at the parent’s exact path.
{
path: "/dashboard",
element: <DashboardLayout />,
children: [
{ index: true, element: <Overview /> },
{ path: "settings", element: <Settings /> }
]
}- Relative paths: The child
"settings"resolves to/dashboard/settings, avoiding duplicated parent segments.
G. Protected routes
Protected routes restrict rendering based on authentication or authorization state, while the server remains responsible for actual data security.
- Authentication check: A guard waits for session loading, renders permitted content, or redirects unauthenticated users.
- Redirect pattern:
<Navigate to="/login" replace state={{ from: location }} />can preserve the intended destination. - Authorization: Role checks distinguish authenticated users from users permitted to access an administrative view.
- Security boundary: Hiding a route is not sufficient protection; APIs must independently verify tokens, sessions, and permissions.
- Loading state: Do not redirect until authentication initialization finishes, or valid users may briefly be sent to login.
H. Route-based code splitting
Route-based code splitting loads JavaScript for a view only when that route is needed.
- Dynamic import:
React.lazyconverts animport()promise into a lazily loaded component. - Fallback UI:
Suspensedisplays temporary content while the route bundle downloads.
const Reports = lazy(() => import("./Reports"));
<Suspense fallback={<Spinner />}>
<Reports />
</Suspense>- Benefit: Large administration or reporting pages are removed from the initial bundle, improving initial download and parse time.
- Trade-off: The first visit to a lazy route may pause; route prefetching can reduce that delay.
- Chunk failure: An error boundary should handle failed dynamic imports caused by network errors or outdated deployments.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →