βš›οΈ 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

  1. Why React for a .NET Engineer?
  2. Core Concepts
  3. State Management
  4. Component Lifecycle & Re-renders
  5. Performance Optimization
  6. PWA β€” Progressive Web App
  7. React vs Blazor WebAssembly
  8. 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:

ConceptReactBlazor WASM
ComponentFunction + JSX.razor file + @code block
PropsFunction parameters[Parameter] attribute
Child contentchildren propChildContent 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.

HookWhat It DoesBlazor Equivalent
useStateLocal component state@code { string name; }
useEffectSide effects (API calls, timers)OnInitializedAsync, OnAfterRenderAsync
useContextAccess global state without passing propsCascading parameters / @inject
useRefReference to a DOM elementElementReference
useMemoMemoize expensive calculationsManual caching
useCallbackMemoize functions to avoid re-rendersNot 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 it

Like 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

SolutionWhen to UseBlazor Equivalent
Context APISimple global state (theme, auth)Cascading parameters
Zustand / ReduxComplex state (multi-page forms, real-time data)Service classes registered as Scoped/Singleton
React Query / TanStack QueryServer 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:

useEffectRuns
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

PhaseReactBlazor WASM
First loadComponent function runs, useEffect firesOnInitializedAsync runs
State changeComponent re-runs, React diffs DOMStateHasChanged() called, Blazor diffs DOM
After renderuseEffect firesOnAfterRenderAsync fires
CleanupuseEffect return functionIDisposable.Dispose
Re-render triggersetState() calledStateHasChanged() 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 changes

Solution 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?

RequirementWhat It DoesHow You Set It Up
HTTPSSecurity requirementDeploy with SSL
Web ManifestDefines app name, icons, theme colorpublic/manifest.json
Service WorkerEnables offline + cachingservice-worker.js
InstallableUser can β€œinstall” to home screenChrome 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

FeaturePWANative (Flutter/React Native)
Install size~1-5 MB~50-200 MB
OfflineLimited (cached pages)Full
Push notificationsβœ… Yesβœ… Yes
Camera/GPSβœ… Yes (modern browsers)βœ… Yes
App Store❌ Noβœ… Required
UpdatesInstant (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

AspectReactBlazor WASM
Runtime size~40 KB (React + ReactDOM)~2-5 MB (.NET WASM runtime)
First loadFast (small bundle)Slow (must download .NET runtime)
Subsequent loadsCached by browserCached by browser (same)
PerformanceFast (native JS)Fast (WASM is close to native)
LanguageTypeScript/JavaScriptC# (share models with backend!)
EcosystemMassive (npm)Smaller (but growing)
DebuggingBrowser DevToolsπŸ”₯ Excellent (full .NET debug in browser)
SEORequires SSR (Next.js)Requires pre-rendering
MobilePWA or React NativePWA only
Learning curveMedium (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

ConceptReactBlazor WASM
Componentfunction Card() { return <div>; }Card.razor
Stateconst [x, setX] = useState()@code { T x; }
Side effectuseEffect(() => {}, [])OnInitializedAsync()
Propsfunction Card({title})[Parameter] public string Title
Global stateContext API / ZustandCascading parameter / Service
MemoizeuseMemo, useCallbackShouldRender() override
Conditional render{condition && <Component/>}@if (condition) { <Component/> }
List render{items.map(i => <Item/>)}@foreach (var i in items) { <Item/> }
EventonClick={handler}@onclick="Handler"
Two-way bindingvalue={x} onChange={e => setX(e.target.value)}@bind-Value="x"

Related: