Skip to content

Commit a641dcf

Browse files
authored
State preservation (common provider) updates (#34337)
1 parent 9092b76 commit a641dcf

File tree

2 files changed

+40
-17
lines changed

2 files changed

+40
-17
lines changed

aspnetcore/blazor/components/cascading-values-and-parameters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,4 @@ The following `ExampleTabSet` component uses the `TabSet` component, which conta
517517
## Additional resources
518518

519519
* [Generic type support: Explicit generic types based on ancestor components](xref:blazor/components/generic-type-support#explicit-generic-types-based-on-ancestor-components)
520-
* [State management: Factor out the state preservation to a common location](xref:blazor/state-management?pivots=server#factor-out-the-state-preservation-to-a-common-location)
520+
* [State management: Factor out state preservation to a common provider](xref:blazor/state-management?pivots=server#factor-out-state-preservation-to-a-common-provider)

aspnetcore/blazor/state-management.md

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,17 @@ else
399399

400400
:::moniker-end
401401

402-
### Factor out the state preservation to a common location
402+
### Factor out state preservation to a common provider
403403

404404
If many components rely on browser-based storage, implementing state provider code many times creates code duplication. One option for avoiding code duplication is to create a *state provider parent component* that encapsulates the state provider logic. Child components can work with persisted data without regard to the state persistence mechanism.
405405

406-
In the following example of a `CounterStateProvider` component, counter data is persisted to `sessionStorage`:
406+
In the following example of a `CounterStateProvider` component, counter data is persisted to `sessionStorage`, and it handles the loading phase by not rendering its child content until state loading is complete.
407+
408+
The `CounterStateProvider` component deals with prerendering by not loading state until after component rendering in the [`OnAfterRenderAsync` lifecycle method](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync), which doesn't execute during prerendering.
409+
410+
The approach in this section isn't capable of triggering rerendering of multiple subscribed components on the same page. If one subscribed component changes the state, it rerenders and can display the updated state, but a different component on the same page displaying that state displays stale data until its own next rerender. Therefore, the approach described in this section is best suited to using state in a single component on the page.
411+
412+
`CounterStateProvider.razor`:
407413

408414
:::moniker range=">= aspnetcore-5.0"
409415

@@ -430,15 +436,26 @@ else
430436
431437
public int CurrentCount { get; set; }
432438
433-
protected override async Task OnInitializedAsync()
439+
protected override async Task OnAfterRenderAsync(bool firstRender)
440+
{
441+
if (firstRender)
442+
{
443+
isLoaded = true;
444+
await LoadStateAsync();
445+
StateHasChanged();
446+
}
447+
}
448+
449+
private async Task LoadStateAsync()
434450
{
435451
var result = await ProtectedSessionStore.GetAsync<int>("count");
436452
CurrentCount = result.Success ? result.Value : 0;
437-
isLoaded = true;
453+
isConnected = true;
438454
}
439455
440-
public async Task SaveChangesAsync()
456+
public async Task IncrementCount()
441457
{
458+
CurrentCount++;
442459
await ProtectedSessionStore.SetAsync("count", CurrentCount);
443460
}
444461
}
@@ -452,7 +469,7 @@ else
452469
@using Microsoft.AspNetCore.ProtectedBrowserStorage
453470
@inject ProtectedSessionStorage ProtectedSessionStore
454471
455-
@if (isLoaded)
472+
@if (isConnected)
456473
{
457474
<CascadingValue Value="this">
458475
@ChildContent
@@ -464,21 +481,32 @@ else
464481
}
465482
466483
@code {
467-
private bool isLoaded;
484+
private bool isConnected;
468485
469486
[Parameter]
470487
public RenderFragment ChildContent { get; set; }
471488
472489
public int CurrentCount { get; set; }
473490
474-
protected override async Task OnInitializedAsync()
491+
protected override async Task OnAfterRenderAsync(bool firstRender)
492+
{
493+
if (firstRender)
494+
{
495+
isConnected = true;
496+
await LoadStateAsync();
497+
StateHasChanged();
498+
}
499+
}
500+
501+
private async Task LoadStateAsync()
475502
{
476503
CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
477-
isLoaded = true;
504+
isConnected = true;
478505
}
479506
480-
public async Task SaveChangesAsync()
507+
public async Task IncrementCount()
481508
{
509+
CurrentCount++;
482510
await ProtectedSessionStore.SetAsync("count", CurrentCount);
483511
}
484512
}
@@ -489,8 +517,6 @@ else
489517
> [!NOTE]
490518
> For more information on <xref:Microsoft.AspNetCore.Components.RenderFragment>, see <xref:blazor/components/index#child-content-render-fragments>.
491519
492-
The `CounterStateProvider` component handles the loading phase by not rendering its child content until state loading is complete.
493-
494520
:::moniker range=">= aspnetcore-8.0"
495521

496522
To make the state accessible to all components in an app, wrap the `CounterStateProvider` component around the <xref:Microsoft.AspNetCore.Components.Routing.Router> (`<Router>...</Router>`) in the `Routes` component with global interactive server-side rendering (interactive SSR).
@@ -541,17 +567,14 @@ Wrapped components receive and can modify the persisted counter state. The follo
541567
{
542568
if (CounterStateProvider is not null)
543569
{
544-
CounterStateProvider.CurrentCount++;
545-
await CounterStateProvider.SaveChangesAsync();
570+
await CounterStateProvider.IncrementCount();
546571
}
547572
}
548573
}
549574
```
550575

551576
The preceding component isn't required to interact with `ProtectedBrowserStorage`, nor does it deal with a "loading" phase.
552577

553-
To deal with prerendering as described earlier, `CounterStateProvider` can be amended so that all of the components that consume the counter data automatically work with prerendering. For more information, see the [Handle prerendering](#handle-prerendering) section.
554-
555578
In general, the *state provider parent component* pattern is recommended:
556579

557580
* To consume state across many components.

0 commit comments

Comments
 (0)