Skip to content

Commit fd2fb50

Browse files
authored
📦 Include JS code (#8)
📦 Manually adding JS code is no longer necessary - all required JS code now comes included with the package. 🐛 Fixed a race condition on page initialization leading to the page sometimes failing to initialize. ⌚ Fixed descendant components continuing to wait for the state root (page) to initialize after it has failed to initialize. 📖 Removed step 5 (`Include the necessary JS code`) from 'Getting Started' documentation page. 📖 Minor updates to the sample app.
1 parent 2ff83d8 commit fd2fb50

22 files changed

+99
-55
lines changed

BitzArt.Blazor.State.sln

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ EndProject
2020
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{56DAC196-1ECD-4485-A227-1A239FCC9866}"
2121
ProjectSection(SolutionItems) = preProject
2222
docs\src\01.introduction.md = docs\src\01.introduction.md
23+
docs\src\02.getting-started.md = docs\src\02.getting-started.md
24+
docs\src\03.state-hierarchy.md = docs\src\03.state-hierarchy.md
25+
docs\src\04.persistent-component-lifecycle.md = docs\src\04.persistent-component-lifecycle.md
2326
docs\src\SUMMARY.md = docs\src\SUMMARY.md
2427
EndProjectSection
2528
EndProject

docs/src/02.getting-started.md

+1-14
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,7 @@ Any page containing persistent components, must also be stateful. This is a requ
6464

6565
To learn more about page state hierarchy, refer to the [State Hierarchy](03.state-hierarchy.md) section.
6666

67-
#### 5. Include the necessary JS code
68-
69-
> ⚠️
70-
> This is a temporary step until a better solution is implemented. We expect this step to go away in one of the future releases.
71-
72-
Include [this](https://github.com/BitzArt/Blazor.State/blob/main/sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp.Client/wwwroot/app.js) JS file in your **Client** project's `wwwroot` folder.
73-
74-
Then, also include the following line in your `App.razor`:
75-
76-
```html
77-
<script src="app.js"></script>
78-
```
79-
80-
#### 6. Enjoy
67+
#### 5. Enjoy
8168

8269
That's it! Your components' state should now be persisted across rendering environments.
8370

sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp.Client/Components/Counter.razor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public partial class Counter : PersistentComponentBase
1212

1313
[Parameter]
1414
public EventCallback<object> OnStateInitialized { get; set; }
15-
15+
1616
[ComponentState]
1717
private int _count = 0;
1818

sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp.Client/Pages/CounterPage.razor.cs

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
1-

2-
namespace BitzArt.Blazor.State.SampleApp.Client.Pages;
1+
namespace BitzArt.Blazor.State.SampleApp.Client.Pages;
32

43
public partial class CounterPage : PersistentComponentBase
54
{
6-
private readonly ComponentPrerequisiteCallback _callback = new();
7-
8-
public CounterPage()
9-
{
10-
// this prerequisite needs to be cancelled manually via the callback
11-
Prerequisites.AddManual(() => _descendantsInitializedCount > 0, _callback, true);
12-
13-
// this prerequisite will automatically check for completion every 100ms
14-
Prerequisites.AddAuto(100, () => _descendantsInitializedCount > 0, true);
15-
}
165

176
[ComponentState]
187
private string _stateText = "Not Initialized";
@@ -37,6 +26,6 @@ private void OnDescendantInitialized()
3726
{
3827
_descendantsInitializedCount++;
3928

40-
if (_descendantsInitializedCount > 0) _callback.Invoke();
29+
StateHasChanged();
4130
}
4231
}

sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp.Client/wwwroot/app.js

-7
This file was deleted.

sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp/Components/App.razor

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
<body>
1616
<Routes />
1717
<script src="_framework/blazor.web.js"></script>
18-
<script src="app.js"></script>
1918
</body>
2019

2120
</html>

sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp/Program.cs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public static void Main(string[] args)
2020

2121
var app = builder.Build();
2222

23+
app.UseWebAssemblyDebugging();
24+
2325
app.MapStaticAssets();
2426
app.UseStaticFiles();
2527
app.UseAntiforgery();

sample/BitzArt.Blazor.State.SampleApp/BitzArt.Blazor.State.SampleApp/Properties/launchSettings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"http": {
1313
"commandName": "Project",
1414
"dotnetRunMessages": true,
15-
"launchBrowser": false,
15+
"launchBrowser": true,
1616
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
1717
"applicationUrl": "http://localhost:5201",
1818
"environmentVariables": {

src/BitzArt.Blazor.RenderStrategies/BitzArt.Blazor.RenderStrategies.csproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
44
<TargetFrameworks>net9.0</TargetFrameworks>
@@ -21,6 +21,10 @@
2121
<None Include="..\..\README.md" Pack="True" Visible="False" PackagePath="\" />
2222
</ItemGroup>
2323

24+
<ItemGroup>
25+
<SupportedPlatform Include="browser" />
26+
</ItemGroup>
27+
2428
<ItemGroup>
2529
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
2630
<_Parameter1>BitzArt.Blazor.State</_Parameter1>

src/BitzArt.Blazor.RenderStrategies/Components/StrategyRenderedComponent.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public abstract class StrategyRenderedComponent : IComponent, IHandleAfterRender
2828
/// before proceeding with further rendering and initializing descendant components. <br/>
2929
/// Default value is <c>false</c>.
3030
/// </summary>
31-
protected internal virtual bool ShouldWaitForCompleteInitialization => false;
31+
protected internal virtual bool ShouldWaitForCompleteInitialization => RenderStrategy!.ShouldWaitForCompleteInitialization;
3232

3333
[Inject]
3434
internal IServiceProvider ServiceProvider

src/BitzArt.Blazor.RenderStrategies/Prerequisites/AutomaticComponentPrerequisite.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Diagnostics.CodeAnalysis;
2-
using System.Threading;
32

43
namespace BitzArt.Blazor;
54

@@ -89,7 +88,7 @@ public AutomaticComponentPrerequisite(int period, Func<bool> requirement, bool a
8988
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
9089
/// <param name="constraint"><inheritdoc cref="ComponentPrerequisite.Constraint" path="/summary"/></param>
9190
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, Func<bool> constraint)
92-
:this(period, requirement)
91+
: this(period, requirement)
9392
{
9493
Constraint = constraint;
9594
}
@@ -101,7 +100,7 @@ public AutomaticComponentPrerequisite(int period, Func<bool> requirement, Func<b
101100
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
102101
[SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "In order to have a distinct comment for this constructor")]
103102
public AutomaticComponentPrerequisite(int period, Func<bool> requirement)
104-
:base(requirement)
103+
: base(requirement)
105104
{
106105
Period = period;
107106
}

src/BitzArt.Blazor.RenderStrategies/Prerequisites/ComponentPrerequisite.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public ComponentPrerequisite(Func<bool> requirement, bool allowComponentInitiali
123123
/// <param name="requirement"><inheritdoc cref="Requirement" path="/summary"/></param>
124124
/// <param name="constraint"><inheritdoc cref="Constraint" path="/summary"/></param>
125125
public ComponentPrerequisite(Func<bool> requirement, Func<bool> constraint)
126-
:this(requirement)
126+
: this(requirement)
127127
{
128128
Constraint = constraint;
129129
}

src/BitzArt.Blazor.RenderStrategies/Prerequisites/ComponentPrerequisiteCollection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public void Remove(ComponentPrerequisite prerequisite)
262262
{
263263
var success = _prerequisites.Remove(prerequisite);
264264

265-
if (!success)throw new InvalidOperationException(
265+
if (!success) throw new InvalidOperationException(
266266
"The specified prerequisite was not found in the collection.");
267267

268268
Unmap(prerequisite);

src/BitzArt.Blazor.RenderStrategies/Strategies/ComponentRenderStrategy.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ internal class ComponentRenderStrategy : IComponentRenderStrategy
1414
public bool IsInitialized { get; private set; } = false;
1515
public bool IsReady { get; private set; } = false;
1616

17+
public virtual bool ShouldWaitForCompleteInitialization => false;
18+
1719
public RenderHandle Handle { get; set; }
1820

1921
private RenderFragment RenderFragment
@@ -33,7 +35,7 @@ private RenderFragment RenderFragment
3335
{
3436
get
3537
{
36-
38+
3739
if (!_renderMode.cached)
3840
{
3941
_renderMode = (Handle.RenderMode, true);

src/BitzArt.Blazor.RenderStrategies/Strategies/IComponentRenderStrategy.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ public interface IComponentRenderStrategy
1212

1313
internal bool IsReady { get; }
1414

15+
internal bool ShouldWaitForCompleteInitialization { get; }
16+
1517
internal IServiceProvider ServiceProvider { get; set; }
1618

1719
internal RenderHandle Handle { get; set; }
18-
20+
1921
internal IComponentRenderMode? AssignedRenderMode { get; }
2022

2123
internal void Attach(RenderHandle renderHandle);
@@ -25,7 +27,7 @@ public interface IComponentRenderStrategy
2527
internal void StateHasChanged();
2628

2729
internal Task CallStateHasChangedOnAsyncCompletion(Task task);
28-
30+
2931
internal Task OnAfterRenderAsync();
3032

3133
/// <summary>

src/BitzArt.Blazor.State/BitzArt.Blazor.State.csproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
44
<TargetFrameworks>net9.0</TargetFrameworks>
@@ -21,6 +21,10 @@
2121
<None Include="..\..\README.md" Pack="True" Visible="False" PackagePath="\" />
2222
</ItemGroup>
2323

24+
<ItemGroup>
25+
<SupportedPlatform Include="browser" />
26+
</ItemGroup>
27+
2428
<ItemGroup>
2529
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
2630
<_Parameter1>BitzArt.Blazor.State.Tests</_Parameter1>

src/BitzArt.Blazor.State/Components/PersistentComponentBase.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ internal PersistentComponentRenderStrategyFactory RenderStrategyFactory
7474
public string? StateId { get; set; }
7575

7676
internal PersistentComponentPositionIdentifier PositionIdentifier
77-
=> StateId is not null ? new (StateId) : new(GetType());
77+
=> StateId is not null ? new(StateId) : new(GetType());
7878

7979
private PersistentComponentRenderStrategy PersistentRenderStrategy
8080
=> (PersistentComponentRenderStrategy)RenderStrategy!;
@@ -100,6 +100,16 @@ protected virtual Task InitializeStateAsync()
100100
internal Task InitializeStateInternalAsync()
101101
=> InitializeStateAsync();
102102

103+
internal void OnStateRestoreFailedInternal()
104+
{
105+
StateRestoreFailed = true;
106+
OnStateRestoreFailedEvent?.Invoke();
107+
}
108+
109+
internal delegate void OnStateRestoreFailedHandler();
110+
internal event OnStateRestoreFailedHandler? OnStateRestoreFailedEvent;
111+
internal bool StateRestoreFailed = false;
112+
103113
internal void OnStateRestoredInternal()
104114
{
105115
OnStateRestoredEvent?.Invoke();

src/BitzArt.Blazor.State/RenderStrategies/PersistentComponentRenderStrategy.cs

+19-2
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,29 @@ private async Task WaitForPageStateAsync()
4444
if (PersistentComponent.StateRoot!.StateInitialized) return;
4545

4646
using var cts = new CancellationTokenSource();
47+
48+
bool failed = PersistentComponent.StateRoot!.StateRestoreFailed;
49+
if (failed) throw new InvalidOperationException("Failed to restore page state.");
50+
4751
PersistentComponent.StateRoot!.OnStateRestoredEvent += cts.Cancel;
52+
PersistentComponent.StateRoot!.OnStateRestoreFailedEvent += () =>
53+
{
54+
failed = true;
55+
cts.Cancel();
56+
};
57+
58+
failed = PersistentComponent.StateRoot!.StateRestoreFailed;
59+
if (failed) throw new InvalidOperationException("Failed to restore page state.");
60+
4861
using var timeoutTask = Task.Delay(5000, cts.Token);
62+
4963
if (cts.IsCancellationRequested) return;
5064
if (PersistentComponent.StateRoot!.StateInitialized) return;
5165

5266
await timeoutTask.IgnoreCancellation();
5367

68+
if (failed) throw new InvalidOperationException("Failed to restore page state.");
69+
5470
if (timeoutTask.Status == TaskStatus.RanToCompletion)
5571
throw new TimeoutException("Timed out: Page state took too long to restore.");
5672
}
@@ -64,7 +80,7 @@ private protected virtual async Task<bool> TryRestoringStateAsync()
6480

6581
var rootStrategy = PersistentComponent.StateRoot!.RenderStrategy;
6682
if (rootStrategy is not PersistentPageRenderStrategy pageStrategy)
67-
throw new InvalidOperationException("The root stateful component is not a page. Make sure your page inherits from PersistentComponentBase.");
83+
throw new InvalidOperationException("The root stateful component is not a page. Make sure your pages containing persistent components also inherit from PersistentComponentBase.");
6884

6985
var pageState = pageStrategy.PageState;
7086

@@ -79,10 +95,11 @@ private protected virtual async Task<bool> TryRestoringStateAsync()
7995

8096
if (state is null)
8197
{
98+
PersistentComponent.OnStateRestoreFailedInternal();
8299
// Component state not found.
83100
return false;
84101
}
85-
102+
86103
RestoreComponentState(state);
87104
StateInitialized = true;
88105

src/BitzArt.Blazor.State/RenderStrategies/PersistentPageRenderStrategy.cs

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ internal class PersistentPageRenderStrategy(PersistentComponentBase component)
1313
{
1414
protected override bool ShouldWaitForRootStateRestore => false;
1515

16+
public override bool ShouldWaitForCompleteInitialization => true;
17+
1618
private bool _shouldPersistState = false;
1719

1820
internal PersistentPageState? PageState { get; private set; }
@@ -55,7 +57,21 @@ internal override void BuildRenderTree(RenderTreeBuilder builder)
5557
private protected override async Task<bool> TryRestoringStateAsync()
5658
{
5759
var js = ServiceProvider.GetRequiredService<IJSRuntime>();
58-
var stateBase64 = await js.InvokeAsync<string?>("getInnerText", [PageStateElementId]);
60+
var module = await js.InvokeAsync<IJSObjectReference>("import", "./_content/BitzArt.Blazor.State/state.js");
61+
62+
string? stateBase64 = null;
63+
64+
await Task.Delay(100);
65+
66+
try
67+
{
68+
stateBase64 = await module.InvokeAsync<string?>("getInnerText", [PageStateElementId]);
69+
}
70+
finally
71+
{
72+
await module.DisposeAsync();
73+
}
74+
5975

6076
if (stateBase64 is null)
6177
{
@@ -74,6 +90,8 @@ private protected override async Task<bool> TryRestoringStateAsync()
7490
ServiceProvider.GetRequiredService<ILogger<PersistentPageRenderStrategy>>()
7591
.LogWarning("State container was not found on page. Initializing state as a fallback. Ignore this warning if prerendering is disabled for this page.");
7692

93+
PersistentComponent.OnStateRestoreFailedInternal();
94+
7795
return false;
7896
}
7997

src/BitzArt.Blazor.State/Services/PersistentComponentStatePropertyMap.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public PersistentComponentStateInfo GetComponentStateInfo(Type componentType)
1010
{
1111
if (!Components.TryGetValue(componentType, out var componentStateInfo))
1212
throw new InvalidOperationException($"Component {componentType.FullName} is not registered.");
13-
13+
1414
return componentStateInfo;
1515
}
1616
}

src/BitzArt.Blazor.State/Services/StateJsonSerializerOptions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Text.Json.Serialization;
2-
using System.Text.Json;
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
33

44
namespace BitzArt.Blazor.State;
55

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function getInnerText(id) {
2+
3+
console.debug('getInnerText was called');
4+
console.debug('document', document);
5+
6+
let element = document.getElementById(id);
7+
8+
console.debug('element', element);
9+
10+
if (element) {
11+
return element.innerText;
12+
}
13+
14+
return null;
15+
}

0 commit comments

Comments
 (0)