Technical Blog Article — App 63: Async Search with React, TypeScript, Vite, and Fluent UI

Introduction

Modern enterprise applications depend heavily on asynchronous data retrieval. Almost every professional React system eventually needs to:

  • search remote data
  • retrieve users
  • query APIs
  • display dynamic results
  • synchronize UI with external systems
  • handle loading and error states

This is exactly the purpose of App 63 — Async Search.

This application belongs to Block 4 — Effects and Architecture, where the ReactLab roadmap transitions from local UI rendering into external synchronization, async architecture, effects, services, APIs, and scalable enterprise patterns.

The app simulates a Microsoft-style enterprise search experience using:

  • React
  • TypeScript
  • Vite
  • Fluent UI
  • async/await
  • Fetch API
  • controlled components
  • loading states
  • error handling
  • conditional rendering

This app introduces one of the most important React mental models:

External systems
must synchronize with React state.

Unlike static rendering, asynchronous systems involve:

  • waiting
  • latency
  • failures
  • synchronization
  • external dependencies

Because of this, React applications must manage async workflows carefully.

The key rendering model becomes:

User interaction
→ state update
→ async operation
→ external response
→ React re-render
→ UI updates automatically

This is the foundation of:

  • dashboards
  • SharePoint portals
  • Microsoft 365 integrations
  • search engines
  • GitHub explorers
  • CRM systems
  • admin panels
  • enterprise React applications

1. Project Goal

The purpose of App 63 is to create an enterprise search interface capable of:

  • receiving user search input
  • sending async requests
  • retrieving remote users
  • rendering search results
  • displaying loading feedback
  • handling failures gracefully
  • organizing code professionally

The app intentionally separates:

  • models
  • services
  • components
  • rendering logic

This separation is critical in scalable React applications.


2. React Learn Concepts Introduced

This app directly connects to several important React Learn sections:

React ConceptPurpose
Synchronizing with EffectsAsync synchronization
Reacting to Input with StateControlled search input
Conditional RenderingLoading and error UI
State as Component MemorySearch state
Lifecycle of Reactive EffectsAsync request lifecycle
Keeping Components PureRendering separation

Official React documentation:


3. Create the Project

Create the Block Folder

cd C:\ReactApps
New-Item bloco04 -ItemType Directory
cd bloco04

Create the Vite React Project

npm create vite@latest app63-async-search -- --template react-ts

This creates:

  • React
  • TypeScript
  • Vite configuration
  • development server
  • ESLint integration
  • project structure

Enter the Project

cd app63-async-search

Install Dependencies

npm install

Install Fluent UI

npm install @fluentui/react-components @fluentui/react-icons

Fluent UI provides:

  • Microsoft design system
  • accessibility
  • enterprise components
  • typography
  • responsive layout behavior
  • theme system

Official documentation:


4. Create the Folder Structure

New-Item src\components -ItemType Directory
New-Item src\services -ItemType Directory
New-Item src\models -ItemType Directory
New-Item src\hooks -ItemType Directory
New-Item src\styles -ItemType Directory

5. Create the Files

New-Item src\models\UserResult.ts -ItemType File
New-Item src\services\searchService.ts -ItemType File
New-Item src\components\SearchBar.tsx -ItemType File
New-Item src\components\SearchResults.tsx -ItemType File
New-Item src\components\LoadingState.tsx -ItemType File
New-Item src\components\ErrorMessage.tsx -ItemType File
New-Item artigo.md -ItemType File

6. Final Project Structure

app63-async-search/
src/
components/
ErrorMessage.tsx
LoadingState.tsx
SearchBar.tsx
SearchResults.tsx
services/
searchService.ts
models/
UserResult.ts
hooks/
styles/
App.tsx
main.tsx
index.css

This structure is extremely important because professional React applications should separate:

  • rendering
  • data retrieval
  • models
  • UI concerns
  • orchestration

7. Create the API Model

src\models\UserResult.ts

export interface UserResult {
id: number;
name: string;
email: string;
company: {
name: string;
};
}

This defines the expected API structure.

The app uses the public API:

https://jsonplaceholder.typicode.com/users

TypeScript ensures:

  • predictable rendering
  • safer refactoring
  • autocomplete
  • type validation

Without TypeScript models:

  • API rendering becomes fragile
  • runtime bugs become more likely
  • maintenance becomes harder

8. Create the Service Layer

src\services\searchService.ts

import type { UserResult } from "../models/UserResult";
export async function searchUsers(
query: string
): Promise<UserResult[]> {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
if (!response.ok) {
throw new Error("Failed to fetch users.");
}
const data: UserResult[] =
await response.json();
return data.filter((user) =>
user.name
.toLowerCase()
.includes(query.toLowerCase())
);
}

9. Why the Service Layer Matters

One of the biggest architectural mistakes in beginner React apps is placing:

  • fetch
  • filtering
  • error handling
  • rendering
  • state updates

all inside one giant component.

Instead, this project separates responsibilities:

LayerResponsibility
ComponentsUI rendering
ServicesExternal systems
ModelsData contracts
App.tsxState orchestration

This architecture scales far better in enterprise environments.


10. Understanding async/await

The function:

export async function searchUsers(...)

is asynchronous.

This means it returns a Promise.

Inside:

await fetch(...)

JavaScript pauses until the HTTP request completes.

Without async handling:

  • the app would continue immediately
  • data would not be ready
  • rendering would fail

11. Understanding the Fetch API

The Fetch API performs HTTP requests.

fetch("https://jsonplaceholder.typicode.com/users")

This retrieves remote data.

The response contains:

  • status
  • headers
  • metadata
  • response body

The JSON body is extracted with:

await response.json()

Official documentation:


12. Create the Search Bar

src\components\SearchBar.tsx

import {
Button,
Input,
} from "@fluentui/react-components";
import {
Search24Regular,
} from "@fluentui/react-icons";
interface SearchBarProps {
query: string;
onQueryChange: (value: string) => void;
onSearch: () => void;
}
export function SearchBar({
query,
onQueryChange,
onSearch,
}: SearchBarProps) {
return (
<div
style={{
display: "flex",
gap: "12px",
}}
>
<Input
value={query}
placeholder="Search users..."
onChange={(_, data) =>
onQueryChange(data.value)
}
/>
<Button
appearance="primary"
icon={<Search24Regular />}
onClick={onSearch}
>
Search
</Button>
</div>
);
}

13. Controlled Inputs

The search input is controlled.

value={query}

and:

onChange={...}

This means:

  • React owns the value
  • state becomes the source of truth
  • rendering becomes predictable

This is one of the core React mental models.

Official documentation:


14. Create the Loading Component

src\components\LoadingState.tsx

import {
Spinner,
Text,
} from "@fluentui/react-components";
export function LoadingState() {
return (
<div
style={{
display: "flex",
gap: "12px",
alignItems: "center",
marginTop: "24px",
}}
>
<Spinner />
<Text>
Loading search results...
</Text>
</div>
);
}

15. Why Loading States Matter

Without loading feedback:

  • the interface feels frozen
  • users think the app broke
  • UX becomes confusing

Enterprise applications must always communicate async status.


16. Create the Error Component

src\components\ErrorMessage.tsx

import {
MessageBar,
MessageBarBody,
} from "@fluentui/react-components";
interface ErrorMessageProps {
message: string;
}
export function ErrorMessage({
message,
}: ErrorMessageProps) {
return (
<MessageBar intent="error">
<MessageBarBody>
{message}
</MessageBarBody>
</MessageBar>
);
}

17. Create Search Results

src\components\SearchResults.tsx

import {
Card,
Text,
Title3,
} from "@fluentui/react-components";
import type { UserResult } from "../models/UserResult";
interface SearchResultsProps {
results: UserResult[];
}
export function SearchResults({
results,
}: SearchResultsProps) {
return (
<div
style={{
display: "grid",
gap: "16px",
marginTop: "24px",
}}
>
{results.map((user) => (
<Card
key={user.id}
style={{
padding: "20px",
}}
>
<Title3>{user.name}</Title3>
<Text>{user.email}</Text>
<br />
<Text>
Company: {user.company.name}
</Text>
</Card>
))}
</div>
);
}

18. Rendering Lists with map()

This app uses:

results.map(...)

React transforms data into UI.

This is declarative rendering.

Instead of manually creating DOM nodes, we describe:

  • what the UI should look like
  • based on the data

Official documentation:


19. Create App.tsx

src\App.tsx

import { useState } from "react";
import {
Card,
FluentProvider,
Title1,
Text,
webLightTheme,
} from "@fluentui/react-components";
import { SearchBar } from "./components/SearchBar";
import { SearchResults } from "./components/SearchResults";
import { LoadingState } from "./components/LoadingState";
import { ErrorMessage } from "./components/ErrorMessage";
import { searchUsers } from "./services/searchService";
import type { UserResult } from "./models/UserResult";
function App() {
const [query, setQuery] =
useState("");
const [results, setResults] =
useState<UserResult[]>([]);
const [loading, setLoading] =
useState(false);
const [error, setError] =
useState("");
async function handleSearch() {
try {
setLoading(true);
setError("");
const users =
await searchUsers(query);
setResults(users);
} catch (err) {
setError(
"Search failed. Please try again."
);
} finally {
setLoading(false);
}
}
return (
<FluentProvider theme={webLightTheme}>
<main
style={{
minHeight: "100vh",
backgroundColor: "#f5f5f5",
padding: "48px",
boxSizing: "border-box",
}}
>
<section
style={{
maxWidth: "900px",
margin: "0 auto",
}}
>
<Card
style={{
padding: "32px",
}}
>
<Title1>
Async Enterprise Search
</Title1>
<Text>
Search remote users using
React async architecture.
</Text>
<div
style={{
marginTop: "24px",
}}
>
<SearchBar
query={query}
onQueryChange={setQuery}
onSearch={handleSearch}
/>
</div>
{loading && <LoadingState />}
{error && (
<div
style={{
marginTop: "24px",
}}
>
<ErrorMessage
message={error}
/>
</div>
)}
{!loading &&
!error &&
results.length > 0 && (
<SearchResults
results={results}
/>
)}
</Card>
</section>
</main>
</FluentProvider>
);
}
export default App;

20. Understanding the Rendering Flow

The complete rendering flow becomes:

User types query
→ query state updates
User clicks Search
→ handleSearch runs
loading becomes true
→ spinner appears
searchUsers()
→ async API request
API returns data
→ results state updates
React re-renders
→ SearchResults appear
loading becomes false
→ spinner disappears

This is the heart of async React rendering.


21. Why try/catch/finally Matters

try {
}
catch {
}
finally {
}

This guarantees:

  • loading starts correctly
  • errors are handled
  • loading always stops

Without finally, loading might remain active forever after failures.


22. Conditional Rendering

This app uses:

{loading && <LoadingState />}

and:

{error && <ErrorMessage />}

This is conditional rendering.

The UI changes automatically according to state.

Official documentation:


23. Create main.tsx

src\main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(
document.getElementById("root")!
).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

24. Create index.css

src\index.css

body {
margin: 0;
font-family: "Segoe UI", Arial, sans-serif;
}
* {
box-sizing: border-box;
}

25. Run the Application

Development:

npm run dev

Production validation:

npm run build

Production preview:

npm run preview

26. Technical Summary

ConceptExplanation
Controlled InputReact owns search value
Async/AwaitHandles asynchronous requests
Fetch APIRetrieves remote data
Service LayerSeparates API logic
Loading StateShows async progress
Error StateHandles failures
Conditional RenderingUI derives from state
Fluent UIEnterprise Microsoft UI
TypeScript ModelsSafe API contracts
Declarative RenderingReact renders from state

27. Concept Table

ConceptFileWhy It Matters
API modelUserResult.tsStrong typing
Async servicesearchService.tsExternal synchronization
Search inputSearchBar.tsxControlled component
Loading feedbackLoadingState.tsxBetter UX
Error handlingErrorMessage.tsxGraceful failures
Data renderingSearchResults.tsxDeclarative UI
State orchestrationApp.tsxCoordinates async flow

28. Official Documentation

TopicDocumentation
React LearnReact Learn
Synchronizing with EffectsSynchronizing with Effects
Lifecycle of Reactive EffectsLifecycle of Reactive Effects
Conditional RenderingConditional Rendering
Rendering ListsRendering Lists
Fluent UIFluent UI React Components
Fetch APIMDN Fetch API
ViteVite Guide
TypeScriptTypeScript Docs

29. Final Architectural Insight

The most important lesson from App 63 is:

Remote data does not change the React mental model.

Even with APIs and async systems:

State changes
→ React re-renders
→ UI updates automatically

The app still follows the same declarative architecture introduced in the earlier apps.

The difference is that now React must synchronize with:

  • networks
  • APIs
  • latency
  • asynchronous systems

This is the foundation for:

  • enterprise dashboards
  • SharePoint integrations
  • GitHub explorers
  • CRM search systems
  • admin portals
  • Microsoft 365 applications

Current Project Progress

BlockAppNameStatus
Block 101–20Fundamentals and UICompleted
Block 221–40Interactivity and StateCompleted
Block 341–60Professional Fluent UICompleted
Block 461REST API ConsumptionCompleted
Block 462API DashboardCompleted
Block 463Async SearchCurrent
Block 464GitHub User ExplorerNext

Edvaldo Guimrães Filho Avatar

Published by