βοΈ React Deep Dive β Key Concepts & Blazor WASM Comparison
Written for a senior .NET engineer who uses both React and Blazor. Focus: interview prep and practical understanding.
π Table of Contents
- Why React for a .NET Engineer?
- Core Concepts
- State Management
- Component Lifecycle & Re-renders
- Performance Optimization
- PWA β Progressive Web App
- React vs Blazor WebAssembly
- Cheat Sheet
Why React for a .NET Engineer?
Youβre primarily a .NET backend developer, but youβve used React in:
- POS Air & Gas β React 19 + .NET 10 Web API, JWT auth, PWA
- Portfolio Chatbot β React widget on Vercel
For a senior role, they wonβt quiz you on React hooks syntax. They want to know:
- Do you understand how the frontend talks to the backend?
- Can you make architectural decisions (React vs Blazor)?
- Do you understand state management and performance?
- Can you debug frontend issues?
Core Concepts
Components β The Building Blocks
Everything in React is a component. Think of them like C# classes β reusable, composable, with their own data and behavior.
// Function Component (modern β use this)
function UserProfile({ name, role }: { name: string; role: string }) {
return (
<div>
<h2>{name}</h2>
<p>{role}</p>
</div>
);
}Compare to Blazor:
| Concept | React | Blazor WASM |
|---|---|---|
| Component | Function + JSX | .razor file + @code block |
| Props | Function parameters | [Parameter] attribute |
| Child content | children prop | ChildContent RenderFragment |
JSX β HTML in JavaScript
JSX is syntactic sugar for React.createElement(). It looks like HTML but runs as JavaScript.
// JSX
<button className="btn-primary" onClick={handleClick}>
Save
</button>
// What it compiles to (simplified)
React.createElement('button', { className: 'btn-primary', onClick: handleClick }, 'Save')Key difference: React uses className (not class), htmlFor (not for). Blazor uses plain HTML attributes.
Hooks β The Modern Way
Hooks let you use state and lifecycle features in function components. Before hooks (pre-2019), you needed class components.
| Hook | What It Does | Blazor Equivalent |
|---|---|---|
useState | Local component state | @code { string name; } |
useEffect | Side effects (API calls, timers) | OnInitializedAsync, OnAfterRenderAsync |
useContext | Access global state without passing props | Cascading parameters / @inject |
useRef | Reference to a DOM element | ElementReference |
useMemo | Memoize expensive calculations | Manual caching |
useCallback | Memoize functions to avoid re-renders | Not directly available |
State Management
This is the most common frontend interview topic. How do you share data between components?
Local State β useState
const [count, setCount] = useState(0);
// count = current value
// setCount = function to update itLike a field in a Blazor component:
@code {
private int count = 0;
}Props Drilling β The Problem
// Data starts here
<App>
<Dashboard>
<UserPanel>
<Avatar user={user} /> {/* Passed through 3 levels */}
</UserPanel>
</Dashboard>
</App>This is called props drilling β passing data through components that donβt need it. Itβs messy and hard to maintain.
Solutions to Props Drilling
| Solution | When to Use | Blazor Equivalent |
|---|---|---|
| Context API | Simple global state (theme, auth) | Cascading parameters |
| Zustand / Redux | Complex state (multi-page forms, real-time data) | Service classes registered as Scoped/Singleton |
| React Query / TanStack Query | Server state (API data with caching) | Direct HttpClient calls + manual caching |
Christianβs POS likely uses: Context for auth (JWT token), local state for forms, and direct API calls for everything else β which is exactly right for a smaller app.
Context API β Simple Global State
// 1. Create context
const AuthContext = createContext<AuthState | null>(null);
// 2. Provide at top level
function App() {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
<Dashboard />
</AuthContext.Provider>
);
}
// 3. Consume anywhere
function Avatar() {
const { user } = useContext(AuthContext)!;
return <img src={user.avatar} />;
}Component Lifecycle & Re-renders
The Render Cycle
Every time state or props change, the entire component re-renders (the function runs again). This is different from Blazor, which tracks changes at the component level.
function Counter() {
const [count, setCount] = useState(0);
console.log('Counter rendered!'); // Runs EVERY time count changes
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}useEffect β Side Effects
useEffect runs after the component renders. Use it for:
- API calls
- Subscriptions
- DOM manipulation
- Timers
useEffect(() => {
// This runs AFTER the first render
fetch('/api/products').then(res => res.json()).then(setProducts);
// Optional: cleanup function (runs when component unmounts)
return () => console.log('Cleanup!');
}, []); // Empty array = run once (on mount)Dependency array rules:
useEffect | Runs |
|---|---|
useEffect(() => {...}) | Every render |
useEffect(() => {...}, []) | Once on mount |
useEffect(() => {...}, [count]) | When count changes |
useEffect(() => {...}, [count, name]) | When count OR name changes |
β οΈ Blazor devs often trip here. In Blazor, OnInitializedAsync runs once. In React, you must specify [] dependency array explicitly or it runs every render.
Key vs Blazor
| Phase | React | Blazor WASM |
|---|---|---|
| First load | Component function runs, useEffect fires | OnInitializedAsync runs |
| State change | Component re-runs, React diffs DOM | StateHasChanged() called, Blazor diffs DOM |
| After render | useEffect fires | OnAfterRenderAsync fires |
| Cleanup | useEffect return function | IDisposable.Dispose |
| Re-render trigger | setState() called | StateHasChanged() called |
Performance Optimization
Problem: Unnecessary Re-renders
When a parent re-renders, all children re-render even if their props didnβt change.
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c + 1)}>{count}</button>
<ExpensiveList /> {/* Re-renders every time! */}
</div>
);
}Solution 1: React.memo
Wraps a component to only re-render if its props change (like Blazorβs ShouldRender()).
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
return items.map(item => <div>{item}</div>);
});Solution 2: useMemo & useCallback
// Memoize an expensive calculation
const totalPrice = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]); // Only recalculates when `items` changes
// Memoize a function reference (prevents child re-renders)
const handleSave = useCallback(() => {
saveData(formData);
}, [formData]); // New function only when formData changesSolution 3: Code Splitting (Lazy Loading)
Load components only when needed, not on initial page load.
const AdminPanel = React.lazy(() => import('./AdminPanel'));
function App() {
return (
<Suspense fallback={<Loading />}>
<AdminPanel /> {/* Only loads when rendered */}
</Suspense>
);
}Your POS could lazy-load the admin dashboard since only owner role sees it.
PWA β Progressive Web App
Your POS Air & Gas is a PWA. Hereβs what that means technically:
What Makes an App a PWA?
| Requirement | What It Does | How You Set It Up |
|---|---|---|
| HTTPS | Security requirement | Deploy with SSL |
| Web Manifest | Defines app name, icons, theme color | public/manifest.json |
| Service Worker | Enables offline + caching | service-worker.js |
| Installable | User can βinstallβ to home screen | Chrome prompts automatically |
Service Worker β The Key Piece
A service worker is a JavaScript file that runs in the background, separate from your main app. It acts as a network proxy:
Browser β Service Worker β Network (or Cache)
β
If offline β return cached response
For your POS, the service worker would cache:
- Static assets (React bundle, CSS, images) β always from cache
- API responses (product list, customer list) β network first, cache as fallback
- Critical pages (sales form) β pre-cached for offline use
PWA vs Native App
| Feature | PWA | Native (Flutter/React Native) |
|---|---|---|
| Install size | ~1-5 MB | ~50-200 MB |
| Offline | Limited (cached pages) | Full |
| Push notifications | β Yes | β Yes |
| Camera/GPS | β Yes (modern browsers) | β Yes |
| App Store | β No | β Required |
| Updates | Instant (refresh page) | Via app store |
React vs Blazor WebAssembly
This is a senior-level question they might ask. Hereβs the honest comparison:
Architecture Differences
React:
βββββββββββββββββββ
β JavaScript β β Runs in browser natively
β (your code) β
β React runtime β β ~40KB gzipped
β V8 / SpiderMonkeyβ β Browser's JS engine
βββββββββββββββββββ
Blazor WASM:
βββββββββββββββββββ
β C# (your code) β β Compiled to .NET IL
β .NET WASM Runtimeβ β ~2-5 MB download!
β WebAssembly β β Browser's WASM engine
βββββββββββββββββββ
Comparison Table
| Aspect | React | Blazor WASM |
|---|---|---|
| Runtime size | ~40 KB (React + ReactDOM) | ~2-5 MB (.NET WASM runtime) |
| First load | Fast (small bundle) | Slow (must download .NET runtime) |
| Subsequent loads | Cached by browser | Cached by browser (same) |
| Performance | Fast (native JS) | Fast (WASM is close to native) |
| Language | TypeScript/JavaScript | C# (share models with backend!) |
| Ecosystem | Massive (npm) | Smaller (but growing) |
| Debugging | Browser DevTools | π₯ Excellent (full .NET debug in browser) |
| SEO | Requires SSR (Next.js) | Requires pre-rendering |
| Mobile | PWA or React Native | PWA only |
| Learning curve | Medium (JS quirks) | Low (if you know C#) |
When to Pick Each
Pick React when:
- You need a large ecosystem of UI libraries (MUI, Chakra, Ant Design)
- First-load performance is critical (public-facing site)
- You want PWA + potential React Native mobile app
- Your team knows JavaScript
Pick Blazor WASM when:
- You have a pure .NET team (no JS specialists)
- You want to share C# models between backend and frontend
- Debugging is critical (full .NET debug in browser)
- The app is internal/corporate (slower first load is acceptable)
- You hate JavaScript π
Your Real Experience
At CAD-IT, you worked on:
- TMS (Blazor WASM) β The choice made sense: .NET team, complex business logic, internal users who can tolerate the initial load
- POS Air & Gas (React + PWA) β The right choice: needs fast first load, PWA for mobile, broader ecosystem for UI components
If asked in interview: This is a strong answer because it shows you understand trade-offs, not just βI like X better.β
π§ͺ Cheat Sheet
| Concept | React | Blazor WASM |
|---|---|---|
| Component | function Card() { return <div>; } | Card.razor |
| State | const [x, setX] = useState() | @code { T x; } |
| Side effect | useEffect(() => {}, []) | OnInitializedAsync() |
| Props | function Card({title}) | [Parameter] public string Title |
| Global state | Context API / Zustand | Cascading parameter / Service |
| Memoize | useMemo, useCallback | ShouldRender() override |
| Conditional render | {condition && <Component/>} | @if (condition) { <Component/> } |
| List render | {items.map(i => <Item/>)} | @foreach (var i in items) { <Item/> } |
| Event | onClick={handler} | @onclick="Handler" |
| Two-way binding | value={x} onChange={e => setX(e.target.value)} | @bind-Value="x" |
Related: