@@ -47,7 +47,9 @@ If you've used Zustand in React, you'll feel right at home.
4747dotnet add package EasyAppDev.Blazor.Store
4848```
4949
50- ** Requirements:** .NET 8.0+ • Blazor Server or WebAssembly • 38 KB gzipped
50+ ** Requirements:** .NET 8.0+ • Blazor Server, WebAssembly, or Blazor Web App • 38 KB gzipped
51+
52+ > ** Note for Blazor Server/Web App users** : See [ compatibility notes] ( #-blazor-server--blazor-web-app-compatibility ) below
5153
5254### 1. Define Your State
5355
@@ -91,6 +93,251 @@ Inherit from `StoreComponent<T>` and call `Update()`. No actions, no reducers, n
9193
9294---
9395
96+ ## 🎯 Blazor Render Modes: Server, WebAssembly & Auto
97+
98+ ** One library, three render modes, zero configuration changes!**
99+
100+ The library automatically adapts to your Blazor render mode with ** intelligent lazy initialization** . Use the same code everywhere and let the library handle the differences.
101+
102+ ### Quick Comparison
103+
104+ | Feature | Server | WebAssembly | Auto (Server→WASM) |
105+ | ---------| --------| -------------| -------------------|
106+ | ** Core State Management** | ✅ Full | ✅ Full | ✅ Full |
107+ | ** Async Helpers** | ✅ All work | ✅ All work | ✅ All work |
108+ | ** Components & Updates** | ✅ Perfect | ✅ Perfect | ✅ Perfect |
109+ | ** Logging Middleware** | ✅ Works | ✅ Works | ✅ Works |
110+ | ** Redux DevTools** | ⚠️ Gracefully skips | ✅ Works | ✅ Activates after transition |
111+ | ** LocalStorage Persistence** | ❌ Not available | ✅ Works | ✅ Works after transition |
112+ | ** Code Changes Needed** | ✅ None | ✅ None | ✅ None |
113+
114+ ### Understanding Render Modes
115+
116+ #### 🟦 ** Blazor Server**
117+ - Runs on the server via SignalR
118+ - UI updates sent over WebSocket
119+ - ` IJSRuntime ` is scoped (not available at startup)
120+ - ** DevTools** : Gracefully skips (no JavaScript at startup)
121+ - ** Persistence** : Not available (use server-side storage instead)
122+
123+ #### 🟩 ** Blazor WebAssembly**
124+ - Runs entirely in browser
125+ - Downloads .NET runtime to client
126+ - ` IJSRuntime ` always available
127+ - ** DevTools** : ✅ Full support
128+ - ** Persistence** : ✅ Full support
129+
130+ #### 🟨 ** Blazor Auto** (Server → WebAssembly)
131+ - ** Phase 1** : Starts on server (fast initial load)
132+ - ** Phase 2** : Downloads WASM in background
133+ - ** Phase 3** : Seamlessly transitions to client-side
134+ - ** DevTools** : Automatically activates after transition!
135+ - ** Persistence** : Works after transition
136+
137+ ### Universal Configuration (Works Everywhere!)
138+
139+ ** Recommended setup for all modes:**
140+ ``` csharp
141+ // Program.cs - Same code works in Server, WASM, and Auto!
142+ builder .Services .AddStoreUtilities ();
143+
144+ builder .Services .AddStore (
145+ new CounterState (0 ),
146+ (store , sp ) => store .WithDefaults (sp , " Counter" ));
147+ ```
148+
149+ ** What happens in each mode:**
150+
151+ | Render Mode | Behavior |
152+ | -------------| ----------|
153+ | ** Server** | DevTools silently skips, logging works, app runs perfectly |
154+ | ** WebAssembly** | DevTools active immediately, all features work |
155+ | ** Auto** | DevTools inactive initially, activates automatically after WASM loads |
156+
157+ ### How Auto Mode Works (Behind the Scenes)
158+
159+ ```
160+ ┌─────────────────────────────────────────────────────────────┐
161+ │ Phase 1: Server Rendering (0-2 seconds) │
162+ ├─────────────────────────────────────────────────────────────┤
163+ │ • User loads page │
164+ │ • Server renders HTML │
165+ │ • Store initializes with WithDefaults() │
166+ │ • DevTools tries to resolve IJSRuntime → Not available │
167+ │ • DevTools marks initialization as failed → Silent skip │
168+ │ • App works perfectly (core features unaffected) │
169+ └─────────────────────────────────────────────────────────────┘
170+ ↓
171+ ┌─────────────────────────────────────────────────────────────┐
172+ │ Phase 2: WASM Loading (background, 2-5 seconds) │
173+ ├─────────────────────────────────────────────────────────────┤
174+ │ • .NET WebAssembly runtime downloads │
175+ │ • User continues interacting with app │
176+ │ • Store updates work normally │
177+ └─────────────────────────────────────────────────────────────┘
178+ ↓
179+ ┌─────────────────────────────────────────────────────────────┐
180+ │ Phase 3: WASM Active (seamless transition) │
181+ ├─────────────────────────────────────────────────────────────┤
182+ │ • Next state update occurs │
183+ │ • DevTools tries to resolve IJSRuntime → Now available! │
184+ │ • DevTools initializes successfully │
185+ │ • Redux DevTools becomes active │
186+ │ • Persistence becomes available │
187+ │ • No user intervention needed! │
188+ └─────────────────────────────────────────────────────────────┘
189+ ```
190+
191+ ### Mode-Specific Configurations
192+
193+ While the universal configuration works everywhere, you can optimize for specific modes:
194+
195+ ** Blazor Server (Optimized)**
196+ ``` csharp
197+ // Skip DevTools entirely to avoid initialization attempts
198+ builder .Services .AddStore (
199+ new CounterState (0 ),
200+ (store , sp ) => store .WithLogging ()); // Just logging, no DevTools
201+ ```
202+
203+ ** Blazor WebAssembly (Full Features)**
204+ ``` csharp
205+ // Enable all features including persistence
206+ builder .Services .AddStoreWithUtilities (
207+ new CounterState (0 ),
208+ (store , sp ) => store
209+ .WithDefaults (sp , " Counter" )
210+ .WithPersistence (sp , " counter-state" )); // Auto-save to LocalStorage
211+ ```
212+
213+ ** Blazor Auto (Recommended Default)**
214+ ``` csharp
215+ // Use WithDefaults - DevTools activates automatically!
216+ builder .Services .AddStoreWithUtilities (
217+ new CounterState (0 ),
218+ (store , sp ) => store .WithDefaults (sp , " Counter" ));
219+ ```
220+
221+ ### Common Scenarios
222+
223+ #### Scenario 1: Pure Server App (No WASM)
224+ ** Best approach** : Skip DevTools, use logging
225+ ``` csharp
226+ builder .Services .AddStore (
227+ new CounterState (0 ),
228+ (store , sp ) => store .WithLogging ());
229+ ```
230+
231+ #### Scenario 2: Progressive Web App (Auto Mode)
232+ ** Best approach** : Use WithDefaults, let it adapt
233+ ``` csharp
234+ builder .Services .AddStore (
235+ new CounterState (0 ),
236+ (store , sp ) => store .WithDefaults (sp , " Counter" ));
237+ ```
238+
239+ #### Scenario 3: SPA with Full Client Features
240+ ** Best approach** : Enable all features
241+ ``` csharp
242+ builder .Services .AddStoreWithUtilities (
243+ new CounterState (0 ),
244+ (store , sp ) => store
245+ .WithDefaults (sp , " Counter" )
246+ .WithPersistence (sp , " app-state" ));
247+ ```
248+
249+ ### Persistence in Server Mode
250+
251+ Since LocalStorage isn't available in pure Server mode, here are alternatives:
252+
253+ ** Option 1: Server-side storage**
254+ ``` csharp
255+ // Use database, session state, or distributed cache
256+ public record UserPreferences (string Theme , string Language )
257+ {
258+ public async Task <UserPreferences > SaveToDatabase (IDbContext db )
259+ {
260+ await db .SaveAsync (this );
261+ return this ;
262+ }
263+ }
264+ ```
265+
266+ ** Option 2: Switch to Auto mode**
267+ ``` csharp
268+ // In Program.cs, add WASM support
269+ builder .Services .AddRazorComponents ()
270+ .AddInteractiveServerComponents ()
271+ .AddInteractiveWebAssemblyComponents (); // Enable Auto mode
272+
273+ // Then use @rendermode InteractiveAuto in components
274+ ```
275+
276+ ### Troubleshooting by Render Mode
277+
278+ ** Server Mode Issues:**
279+ - ✅ Store updates work? → Core functionality is fine
280+ - ⚠️ DevTools not appearing? → Expected behavior, use logging instead
281+ - ❌ Getting IJSRuntime errors? → Remove ` .WithDefaults() ` , use ` .WithLogging() `
282+
283+ ** WebAssembly Mode Issues:**
284+ - ✅ Everything works? → You're all set!
285+ - ⚠️ DevTools not appearing? → Check browser console, install Redux DevTools extension
286+
287+ ** Auto Mode Issues:**
288+ - ⚠️ DevTools delayed? → Normal, waits for WASM transition
289+ - ✅ Store works immediately? → Core features work from initial server render
290+ - ❌ Getting errors on startup? → Check that WASM components are registered
291+
292+ ### Feature Detection
293+
294+ The library automatically detects and adapts:
295+
296+ ``` csharp
297+ // DevToolsMiddleware internal logic (simplified)
298+ private async Task EnsureInitializedAsync ()
299+ {
300+ if (_initialized || _initializationFailed )
301+ return ;
302+
303+ try
304+ {
305+ // Try to resolve IJSRuntime
306+ _jsRuntime = _serviceProvider .GetService <IJSRuntime >();
307+
308+ if (_jsRuntime == null )
309+ {
310+ _initializationFailed = true ; // Server mode
311+ return ;
312+ }
313+
314+ // Initialize DevTools
315+ await _jsRuntime .InvokeAsync (.. .);
316+ _initialized = true ; // Success!
317+ }
318+ catch
319+ {
320+ _initializationFailed = true ; // Graceful failure
321+ }
322+ }
323+ ```
324+
325+ ### Migration Paths
326+
327+ ** From Server to Auto:**
328+ 1 . Add WebAssembly components: ` .AddInteractiveWebAssemblyComponents() `
329+ 2 . Change render mode: ` @rendermode InteractiveAuto `
330+ 3 . No code changes needed in state management!
331+
332+ ** From WASM to Auto:**
333+ 1 . Add Server components: ` .AddInteractiveServerComponents() `
334+ 2 . Change render mode: ` @rendermode InteractiveAuto `
335+ 3 . No code changes needed in state management!
336+
337+ > ** Key Takeaway** : Write your state management code once with ` WithDefaults() ` , and it works perfectly across all render modes with automatic adaptation!
338+
339+ ---
340+
94341## Table of Contents
95342
96343- [ Quick Start] ( #quick-start )
@@ -703,6 +950,25 @@ public State AddItem(Item item)
703950
704951## Troubleshooting
705952
953+ ### "Cannot resolve scoped service 'IJSRuntime' from root provider"?
954+ ✅ ** UPDATED** : This error no longer occurs! The library now uses lazy IJSRuntime resolution.
955+
956+ ** If you're seeing this error** , you're using an old configuration pattern:
957+ ``` csharp
958+ // ❌ Old pattern (caused errors)
959+ var jsRuntime = serviceProvider .GetRequiredService <IJSRuntime >();
960+ builder .Services .AddStore (new State (), store => store .WithDevTools (jsRuntime , " Store" ));
961+
962+ // ✅ New pattern (works everywhere!)
963+ builder .Services .AddStore (
964+ new State (),
965+ (store , sp ) => store .WithDefaults (sp , " Store" )); // Lazy resolution!
966+ ```
967+
968+ The new ` WithDefaults(sp, ...) ` method resolves IJSRuntime lazily, so it works in ** all render modes** (Server, WASM, Auto).
969+
970+ See the [ Blazor Render Modes] ( #-blazor-render-modes-server-webassembly--auto ) section for details.
971+
706972### Component not updating?
707973✅ Inherit from ` StoreComponent<TState> `
708974
0 commit comments