Data import workflow screen showing mapping of CSV fields to system fields with validation options

Technical Blog Article — App 44: Dialog Manager with React, TypeScript, Vite, and Fluent UI

Introduction

In App 44 — Dialog Manager, we build an enterprise-style modal dialog system using React, TypeScript, Vite, and Fluent UI. This app is part of Block 3 — Professional Fluent UI Applications, where the focus moves from basic rendering and state handling into professional Microsoft-style interface patterns.

A dialog is not just a visual popup. In enterprise applications, dialogs are used to guide decisions, confirm risky actions, display alerts, show workflow feedback, collect quick input, and protect users from accidental operations. You will see dialogs in Microsoft 365, SharePoint, Teams, admin portals, CRMs, ERPs, approval systems, ticket systems, dashboards, and configuration panels.

The main objective of this app is to understand that in React, dialog visibility must be controlled by state. We do not manually open and close DOM elements. We update state, React re-renders, and the dialog appears or disappears based on that state.

The core mental model is:

User action
→ event handler
→ state update
→ React re-render
→ dialog visibility changes

This is the React way.


1. What This App Teaches

This app teaches several important concepts:

ConceptMeaning
Dialog stateThe dialog is visible or hidden based on React state
Controlled componentThe parent controls the dialog behavior
Callback propsChild components request state changes from the parent
TypeScript modelDialog structure is typed and predictable
Fluent UI DialogMicrosoft-style accessible modal component
Immutable updatesState objects are updated safely
Declarative renderingUI is derived from state
Component compositionApp is split into small focused components

The key lesson is:

Visibility is state.
Dialogs are UI derived from state.
React controls the rendering.

2. Create the Project

Use PowerShell:

cd C:\ReactApps
New-Item bloco03 -ItemType Directory
cd bloco03
npm create vite@latest app44-dialog-manager -- --template react-ts
cd app44-dialog-manager
npm install
npm install @fluentui/react-components @fluentui/react-icons

Create folders:

New-Item src\components -ItemType Directory
New-Item src\models -ItemType Directory
New-Item src\data -ItemType Directory
New-Item src\styles -ItemType Directory

Create files:

New-Item src\models\DialogState.ts -ItemType File
New-Item src\components\ActionPanel.tsx -ItemType File
New-Item src\components\DialogManager.tsx -ItemType File
New-Item artigo.md -ItemType File

3. Final Folder Structure

app44-dialog-manager/
src/
components/
ActionPanel.tsx
DialogManager.tsx
models/
DialogState.ts
data/
styles/
App.tsx
main.tsx
index.css
artigo.md
package.json
vite.config.ts
tsconfig.json

This structure is important because each file has one responsibility:

FileResponsibility
DialogState.tsDefines the dialog data model
ActionPanel.tsxShows buttons that request dialogs
DialogManager.tsxRenders the Fluent UI dialog
App.tsxOwns the dialog state
main.tsxMounts React into the HTML page
index.cssGlobal CSS reset
artigo.mdTechnical blog article

4. Create the Dialog Model

src\models\DialogState.ts

export type DialogType =
| "success"
| "warning"
| "delete";
export interface DialogState {
open: boolean;
type: DialogType;
title: string;
message: string;
}

This file defines the shape of the dialog state.

The dialog needs to know:

PropertyPurpose
openControls whether the dialog is visible
typeDefines the kind of dialog
titleDefines the dialog title
messageDefines the body message

This is better than creating many separate booleans like:

const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [isWarningOpen, setIsWarningOpen] = useState(false);
const [isSuccessOpen, setIsSuccessOpen] = useState(false);

That approach becomes hard to maintain.

A single typed object is cleaner:

const [dialogState, setDialogState] =
useState<DialogState>(initialDialogState);

5. Create the Action Panel

src\components\ActionPanel.tsx

import {
Button,
Card,
Text,
Title2,
} from "@fluentui/react-components";
import {
CheckmarkCircle24Regular,
Delete24Regular,
Warning24Regular,
} from "@fluentui/react-icons";
import type { DialogState } from "../models/DialogState";
interface ActionPanelProps {
onOpenDialog: (dialog: DialogState) => void;
}
export function ActionPanel({
onOpenDialog,
}: ActionPanelProps) {
return (
<Card
style={{
padding: "32px",
display: "flex",
flexDirection: "column",
gap: "20px",
}}
>
<Title2>Enterprise Actions</Title2>
<Text>
Select an action to open a Fluent UI dialog.
</Text>
<Button
appearance="primary"
icon={<CheckmarkCircle24Regular />}
onClick={() =>
onOpenDialog({
open: true,
type: "success",
title: "Operation Completed",
message:
"The enterprise process completed successfully.",
})
}
>
Success Dialog
</Button>
<Button
appearance="secondary"
icon={<Warning24Regular />}
onClick={() =>
onOpenDialog({
open: true,
type: "warning",
title: "Warning",
message:
"Please review the corporate policy settings before continuing.",
})
}
>
Warning Dialog
</Button>
<Button
appearance="outline"
icon={<Delete24Regular />}
onClick={() =>
onOpenDialog({
open: true,
type: "delete",
title: "Delete Record",
message:
"This action cannot be undone. Confirm before deleting the record.",
})
}
>
Delete Dialog
</Button>
</Card>
);
}

Explanation

ActionPanel does not own the dialog state. It only receives a function:

onOpenDialog: (dialog: DialogState) => void;

This means:

ActionPanel asks the parent to open a dialog.
App owns the state.
DialogManager displays the state.

This is a very important React pattern.

The child component should not directly control global application behavior. It should communicate intent upward through callback props.


6. Create the Dialog Manager

src\components\DialogManager.tsx

import {
Button,
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogSurface,
DialogTitle,
Text,
} from "@fluentui/react-components";
import type { DialogState } from "../models/DialogState";
interface DialogManagerProps {
dialogState: DialogState;
onClose: () => void;
}
export function DialogManager({
dialogState,
onClose,
}: DialogManagerProps) {
return (
<Dialog open={dialogState.open}>
<DialogSurface>
<DialogBody>
<DialogTitle>
{dialogState.title}
</DialogTitle>
<DialogContent>
<Text>
{dialogState.message}
</Text>
</DialogContent>
<DialogActions>
<Button
appearance="primary"
onClick={onClose}
>
Close
</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
);
}

Explanation

The key line is:

<Dialog open={dialogState.open}>

This means the dialog is controlled.

When dialogState.open is true, the dialog appears.

When dialogState.open is false, the dialog disappears.

The UI is not manually manipulated. React renders the correct result from state.


7. Fluent UI Dialog Structure

Fluent UI dialogs use this structure:

Dialog
DialogSurface
DialogBody
DialogTitle
DialogContent
DialogActions

Each part has a clear responsibility:

ComponentPurpose
DialogControls the dialog container
DialogSurfaceProvides the modal visual surface
DialogBodyGroups title, content, and actions
DialogTitleDisplays the title
DialogContentDisplays the body content
DialogActionsContains buttons/actions

This structured approach matters because enterprise dialogs must support accessibility, keyboard navigation, focus management, and consistent design.


8. Create the Root App

src\App.tsx

import { useState } from "react";
import {
FluentProvider,
webLightTheme,
} from "@fluentui/react-components";
import { ActionPanel } from "./components/ActionPanel";
import { DialogManager } from "./components/DialogManager";
import type { DialogState } from "./models/DialogState";
const initialDialogState: DialogState = {
open: false,
type: "success",
title: "",
message: "",
};
function App() {
const [dialogState, setDialogState] =
useState<DialogState>(initialDialogState);
function handleCloseDialog() {
setDialogState({
...dialogState,
open: false,
});
}
return (
<FluentProvider theme={webLightTheme}>
<main
style={{
minHeight: "100vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f5f5f5",
padding: "32px",
boxSizing: "border-box",
}}
>
<section
style={{
width: "420px",
}}
>
<ActionPanel
onOpenDialog={setDialogState}
/>
<DialogManager
dialogState={dialogState}
onClose={handleCloseDialog}
/>
</section>
</main>
</FluentProvider>
);
}
export default App;

9. Understanding useState

This line is the heart of the app:

const [dialogState, setDialogState] =
useState<DialogState>(initialDialogState);

It creates component memory.

PartMeaning
dialogStateCurrent dialog data
setDialogStateFunction used to update dialog data
useStateReact Hook for local state
<DialogState>TypeScript typing
initialDialogStateInitial value

The initial state says:

The dialog starts closed.

10. Understanding the Open Flow

When the user clicks Success Dialog, this runs:

onOpenDialog({
open: true,
type: "success",
title: "Operation Completed",
message: "The enterprise process completed successfully.",
})

That updates the parent state.

Then:

React re-renders App
DialogManager receives new props
Dialog open becomes true
Fluent UI displays the dialog

This is declarative UI.


11. Understanding the Close Flow

When the user clicks Close, this runs:

handleCloseDialog()

The function updates state:

setDialogState({
...dialogState,
open: false,
});

The spread operator keeps the existing title, message, and type, but changes open.

This is an immutable update.

React expects state to be treated as immutable because predictable state updates make rendering safer and easier to debug.


12. Why We Do Not Use useEffect

This app does not need useEffect.

There is no:

  • API request
  • browser event subscription
  • timer
  • localStorage synchronization
  • external system

The dialog is purely controlled by user interaction and React state.

So the correct solution is:

useState, not useEffect.

This is extremely important.

A common beginner mistake is using useEffect for everything. But in React, effects are for synchronizing with external systems. Internal UI state should normally be handled directly with state and event handlers.


13. 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>
);

This file connects React to the browser.

The flow is:

index.html
→ loads main.tsx
→ main.tsx renders App
→ App renders ActionPanel and DialogManager

14. Create index.css

src\index.css

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

This removes the browser default margin and applies a Microsoft-style font.


15. Run the App

Development:

npm run dev

Production build:

npm run build

Preview production build:

npm run preview

16. Complete Rendering Flow

main.tsx
renders App
App
owns dialogState
ActionPanel
receives setDialogState as onOpenDialog
User clicks a button
ActionPanel calls onOpenDialog
App state changes
React re-renders
DialogManager
receives dialogState
Fluent UI Dialog
opens or closes based on dialogState.open

This is the full architecture.


17. Technical Summary

ConceptExplanation
useStateStores dialog visibility and content
DialogStateTypeScript model for modal state
ActionPanelSends dialog open requests
DialogManagerRenders the Fluent UI dialog
Callback propsAllow child-to-parent communication
Controlled dialogDialog visibility comes from React state
Immutable updateState object copied before changing open
Fluent UI DialogAccessible Microsoft-style modal
No useEffectInternal UI behavior does not require effects
CompositionSmall components working together

18. Concept Table

ConceptFileWhy It Matters
Dialog modelDialogState.tsKeeps dialog state typed
Parent stateApp.tsxCentralizes modal control
Callback propActionPanel.tsxChild requests parent update
Controlled modalDialogManager.tsxUI follows state
Fluent UIDialogManager.tsxProvides enterprise dialog structure
TypeScriptAll filesPrevents shape errors
React renderingApp.tsxState changes trigger UI updates
StylingInline + index.cssKeeps layout simple and visible

19. Official Documentation

TopicDocumentation
React Learnhttps://react.dev/learn
State: A Component’s Memoryhttps://react.dev/learn/state-a-components-memory
Sharing State Between Componentshttps://react.dev/learn/sharing-state-between-components
Updating Objects in Statehttps://react.dev/learn/updating-objects-in-state
You Might Not Need an Effecthttps://react.dev/learn/you-might-not-need-an-effect
Fluent UI React Componentshttps://developer.microsoft.com/en-us/fluentui#/controls/web
Fluent UI Dialoghttps://developer.microsoft.com/en-us/fluentui#/controls/web/dialog
Vite Guidehttps://vite.dev/guide/
TypeScript Docshttps://www.typescriptlang.org/docs/

20. Final Didactic Insight

The most important lesson from App 44 is that a dialog is not a separate imperative object that we manually show or hide.

In React, a dialog is just UI derived from state.

The correct mental model is:

State says open = true
→ React shows the dialog
State says open = false
→ React hides the dialog

This same pattern will appear again in:

  • confirmation forms
  • delete records
  • approval workflows
  • edit forms
  • user management
  • ticket systems
  • CRM panels
  • SharePoint-style enterprise interfaces

Mastering dialogs now prepares you for larger Fluent UI applications.


Current Project Progress

BlockAppNameStatus
Block 101Hello React FluentCompleted
Block 102Profile CardCompleted
Block 103Product ListCompleted
Block 104Microsoft Style User CardCompleted
Block 105Static DashboardCompleted
Block 106Corporate Sidebar MenuCompleted
Block 107Visual Task ListCompleted
Block 108Timeline EventsCompleted
Block 109Employee TableCompleted
Block 110Email ListCompleted
Block 111Grid of CardsCompleted
Block 112Image GalleryCompleted
Block 113Movie CatalogCompleted
Block 114Football TeamsCompleted
Block 115News PageCompleted
Block 116Financial DashboardCompleted
Block 117SharePoint Style LayoutCompleted
Block 118File ExplorerCompleted
Block 119Corporate PortalCompleted
Block 120Microsoft Style Landing PageCompleted
Block 221Modern CounterCompleted
Block 222Toggle ThemeCompleted
Block 223React CalculatorCompleted
Block 224Login FormCompleted
Block 225User RegistrationCompleted
Block 226Complete ToDo ListCompleted
Block 227Shopping ListCompleted
Block 228Product FilterCompleted
Block 229Employee SearchCompleted
Block 230Shopping CartCompleted
Block 231Grade SimulatorCompleted
Block 232Inventory ControlCompleted
Block 233Contact AgendaCompleted
Block 234Currency ConverterCompleted
Block 235BMI CalculatorCompleted
Block 236Installment SimulatorCompleted
Block 237Voting PanelCompleted
Block 238Interactive QuizCompleted
Block 239Team ManagerCompleted
Block 240Dynamic DashboardCompleted
Block 341Microsoft Style LoginCompleted
Block 342Corporate FormCompleted
Block 343Tabs NavigationCompleted
Block 344Dialog ManagerCurrent
Block 345Executive DashboardNext

Edvaldo Guimrães Filho Avatar

Published by