From 3d0e703e655ca4b5a41dc80445480e1e99b21e07 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 7 Mar 2024 23:11:08 -0600 Subject: [PATCH 1/7] Update to .NET 8 --- Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj | 2 ++ Source/Lib/Fluxor/Fluxor.csproj | 1 + 2 files changed, 3 insertions(+) diff --git a/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj b/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj index ee56a2b5..6c8c529a 100644 --- a/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj +++ b/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj @@ -12,9 +12,11 @@ + + diff --git a/Source/Lib/Fluxor/Fluxor.csproj b/Source/Lib/Fluxor/Fluxor.csproj index f607192c..d14e36b9 100644 --- a/Source/Lib/Fluxor/Fluxor.csproj +++ b/Source/Lib/Fluxor/Fluxor.csproj @@ -15,6 +15,7 @@ + From b9be89e1109728a189984174a31995f0d206216d Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 7 Mar 2024 23:15:17 -0600 Subject: [PATCH 2/7] Initializing store from OnInitializedAsync instead of OnAfterRenderAsync due to change in lifecycle, but only for .NET 8 --- Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs b/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs index fa244d78..e1462f44 100644 --- a/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs +++ b/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs @@ -48,6 +48,10 @@ protected override void OnInitialized() MiddlewareInitializationScripts = scriptBuilder.ToString(); base.OnInitialized(); } + protected override async Task OnInitializedAsync() + { + await Store.InitializeAsync(); + } protected override void OnAfterRender(bool firstRender) { @@ -72,8 +76,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (!string.IsNullOrWhiteSpace(MiddlewareInitializationScripts)) await JSRuntime.InvokeVoidAsync("eval", MiddlewareInitializationScripts); - - await Store.InitializeAsync(); } catch (JSException err) { From ffb3a3ea03a6014af2a7f27c63d70eece5ef1cd6 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 7 Mar 2024 23:15:34 -0600 Subject: [PATCH 3/7] Added .NET 8-specific preprocessor regions --- Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs b/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs index e1462f44..9dabd3f0 100644 --- a/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs +++ b/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs @@ -48,10 +48,13 @@ protected override void OnInitialized() MiddlewareInitializationScripts = scriptBuilder.ToString(); base.OnInitialized(); } + +#if NET8 protected override async Task OnInitializedAsync() { await Store.InitializeAsync(); } +#endif protected override void OnAfterRender(bool firstRender) { @@ -76,6 +79,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (!string.IsNullOrWhiteSpace(MiddlewareInitializationScripts)) await JSRuntime.InvokeVoidAsync("eval", MiddlewareInitializationScripts); + +#if !NET8 + await Store.InitializeAsync(); +#endif } catch (JSException err) { From d3308bd6fca087dd9f2ac3c16067528a714eb1f5 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 8 Mar 2024 17:20:26 -0600 Subject: [PATCH 4/7] Added fixes identified in #481 comments --- .../Fluxor.Blazor.Web/Components/FluxorComponent.cs | 11 +++++++++++ Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs | 1 + 2 files changed, 12 insertions(+) diff --git a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs index 35c7378e..888d59bd 100644 --- a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs +++ b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs @@ -64,6 +64,17 @@ public void Dispose() } } + /// + /// Initializes the store (primarily intended for WASM components and pages). + /// + protected override void async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + //Attempt to initialize the store knowing that if it's already been initialized, this won't do anything + await Store.InitializeAsync(); + } + /// /// Subscribes to state properties /// diff --git a/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs b/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs index 9dabd3f0..01bc364f 100644 --- a/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs +++ b/Source/Lib/Fluxor.Blazor.Web/StoreInitializer.cs @@ -53,6 +53,7 @@ protected override void OnInitialized() protected override async Task OnInitializedAsync() { await Store.InitializeAsync(); + await base.OnInitializedAsync(); } #endif From a3916d69a9790a2372a527890ae320a66d8691c9 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Mon, 11 Mar 2024 00:44:57 -0500 Subject: [PATCH 5/7] Adding persistence implementation --- .../Components/FluxorComponent.cs | 1 + .../Persistence/PersistenceEffects.cs | 59 ++++++++++++++ .../DependencyInjection/FluxorOptions.cs | 28 +++++++ .../ServiceRegistration/StoreRegistration.cs | 7 ++ Source/Lib/Fluxor/Fluxor.csproj | 3 +- .../Fluxor/Persistence/IPersistenceManager.cs | 27 +++++++ .../Persistence/StorePersistedAction.cs | 13 ++++ .../Persistence/StorePersistingAction.cs | 13 ++++ .../Persistence/StoreRehydratedAction.cs | 13 ++++ .../Persistence/StoreRehydratingAction.cs | 13 ++++ Source/Lib/Fluxor/Store.cs | 77 ++++++++++++++++++- 11 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 Source/Lib/Fluxor.Blazor.Web/Persistence/PersistenceEffects.cs create mode 100644 Source/Lib/Fluxor/Persistence/IPersistenceManager.cs create mode 100644 Source/Lib/Fluxor/Persistence/StorePersistedAction.cs create mode 100644 Source/Lib/Fluxor/Persistence/StorePersistingAction.cs create mode 100644 Source/Lib/Fluxor/Persistence/StoreRehydratedAction.cs create mode 100644 Source/Lib/Fluxor/Persistence/StoreRehydratingAction.cs diff --git a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs index 888d59bd..bc89314a 100644 --- a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs +++ b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs @@ -1,5 +1,6 @@ using Fluxor.UnsupportedClasses; using Microsoft.AspNetCore.Components; +using System.Threading; using System; namespace Fluxor.Blazor.Web.Components diff --git a/Source/Lib/Fluxor.Blazor.Web/Persistence/PersistenceEffects.cs b/Source/Lib/Fluxor.Blazor.Web/Persistence/PersistenceEffects.cs new file mode 100644 index 00000000..56a600b3 --- /dev/null +++ b/Source/Lib/Fluxor.Blazor.Web/Persistence/PersistenceEffects.cs @@ -0,0 +1,59 @@ +using Fluxor.Persistence; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Fluxor.Blazor.Web.Persistence +{ +#if NET6_0_OR_GREATER + public sealed class PersistenceEffects + { + private readonly IPersistenceManager _persistenceManager; + private readonly IServiceProvider _serviceProvider; + + public PersistenceEffects(IPersistenceManager persistenceManager, IServiceProvider serviceProvider) + { + _persistenceManager = persistenceManager; + _serviceProvider = serviceProvider; + } + + /// + /// Maintains a reference to IStore - injected this way to avoid a circular dependency during the effect method registration + /// + private readonly Lazy _store = new(_serviceProvider.GetRequiredService); + + [EffectMethod(typeof(StorePersistingAction))] + public async Task PersistStoreData(IDispatcher dispatcher) + { + //Serialize the store + var json = _store.Value.SerializeToJson(); + + //Save to the persistence manager + await _persistenceManager.PersistStoreToStateAsync(json); + + //Completed + dispatcher.Dispatch(new StorePersistedAction()); + } + + [EffectMethod(typeof(StoreRehydratingAction))] + public async Task RehydrateStoreData(IDispatcher dispatcher) + { + //Read from the persistence manager + var serializedStore = await _persistenceManager.RehydrateStoreFromStateAsync(); + if (serializedStore is null) + { + //Nothing to rehydrate - leave as-is + dispatcher.Dispatch(new StoreRehydratedAction()); + return; + } + + _store.Value.RehydrateFromJson(serializedStore); + + //Completed + dispatcher.Dispatch(new StoreRehydratedAction()); + } + } +#endif +} diff --git a/Source/Lib/Fluxor/DependencyInjection/FluxorOptions.cs b/Source/Lib/Fluxor/DependencyInjection/FluxorOptions.cs index d0c0e848..a6c8427d 100644 --- a/Source/Lib/Fluxor/DependencyInjection/FluxorOptions.cs +++ b/Source/Lib/Fluxor/DependencyInjection/FluxorOptions.cs @@ -1,4 +1,5 @@ using Fluxor.Extensions; +using Fluxor.Persistence; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -138,5 +139,32 @@ public FluxorOptions AddMiddleware() .ToArray(); return this; } + +#if NET6_0_OR_GREATER + + /// + /// Enables automatic store persistence between location state changes. + /// + /// The type of persistence implementation to use. + /// Options + public FluxorOptions WithPersistence() where T : IPersistenceManager + { + Services.AddScoped(typeof(IPersistenceManager), typeof(T)); + return this; + } + + /// + /// Enables automatic store persistence between location state changes. + /// + /// The type of persistence implementation to use. + /// Options + public FluxorOptions WithPersistence(IServiceCollection serviceCollection) where T : IPersistenceManager + { + WithPersistence(); + foreach (var serviceDescriptor in serviceCollection) Services.Add(serviceDescriptor); + return this; + } + +#endif } } diff --git a/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs b/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs index 50929e2d..9d43d6d3 100644 --- a/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs +++ b/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs @@ -1,5 +1,6 @@ using Fluxor.DependencyInjection.WrapperFactories; using Fluxor.Extensions; +using Fluxor.Persistence; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -41,7 +42,13 @@ public static void Register( services.Add(typeof(Store), serviceProvider => { var dispatcher = serviceProvider.GetService(); + +#if NET6_0_OR_GREATER + var persistenceManager = serviceProvider.GetService(); + var store = new Store(dispatcher, persistenceManager); +#else var store = new Store(dispatcher); +#endif foreach (FeatureClassInfo featureClassInfo in featureClassInfos) { var feature = (IFeature)serviceProvider.GetService(featureClassInfo.FeatureInterfaceGenericType); diff --git a/Source/Lib/Fluxor/Fluxor.csproj b/Source/Lib/Fluxor/Fluxor.csproj index d14e36b9..03ffba61 100644 --- a/Source/Lib/Fluxor/Fluxor.csproj +++ b/Source/Lib/Fluxor/Fluxor.csproj @@ -4,7 +4,7 @@ A zero boilerplate Redux/Flux framework for .NET fluxor-small-logo.png Redux Flux DotNet CSharp - True + True @@ -20,5 +20,6 @@ + diff --git a/Source/Lib/Fluxor/Persistence/IPersistenceManager.cs b/Source/Lib/Fluxor/Persistence/IPersistenceManager.cs new file mode 100644 index 00000000..ea7f0ddc --- /dev/null +++ b/Source/Lib/Fluxor/Persistence/IPersistenceManager.cs @@ -0,0 +1,27 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Fluxor.Persistence +{ + public interface IPersistenceManager + { + /// + /// Persists the store to a persisted state. + /// + /// The serialized store data being persisted. + public Task PersistStoreToStateAsync(string serializedStore); + + /// + /// Rehydrates the store from a persisted state. + /// + public Task RehydrateStoreFromStateAsync(); + + /// + /// Clears the store from the persisted state. + /// + public Task ClearStoreFromStateAsync(); + } +} diff --git a/Source/Lib/Fluxor/Persistence/StorePersistedAction.cs b/Source/Lib/Fluxor/Persistence/StorePersistedAction.cs new file mode 100644 index 00000000..99288929 --- /dev/null +++ b/Source/Lib/Fluxor/Persistence/StorePersistedAction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fluxor.Persistence +{ + /// + /// Dispatched by the store once it has been successfully persisted to state. + /// + public sealed class StorePersistedAction + { + } +} diff --git a/Source/Lib/Fluxor/Persistence/StorePersistingAction.cs b/Source/Lib/Fluxor/Persistence/StorePersistingAction.cs new file mode 100644 index 00000000..f37943c1 --- /dev/null +++ b/Source/Lib/Fluxor/Persistence/StorePersistingAction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fluxor.Persistence +{ + /// + /// Dispatched by the store once it has started persisting to state. + /// + public sealed class StorePersistingAction + { + } +} diff --git a/Source/Lib/Fluxor/Persistence/StoreRehydratedAction.cs b/Source/Lib/Fluxor/Persistence/StoreRehydratedAction.cs new file mode 100644 index 00000000..478c9098 --- /dev/null +++ b/Source/Lib/Fluxor/Persistence/StoreRehydratedAction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fluxor.Persistence +{ + /// + /// Dispatched by the store once it has been successfully rehydrated from state. + /// + public sealed class StoreRehydratedAction + { + } +} diff --git a/Source/Lib/Fluxor/Persistence/StoreRehydratingAction.cs b/Source/Lib/Fluxor/Persistence/StoreRehydratingAction.cs new file mode 100644 index 00000000..62d0404f --- /dev/null +++ b/Source/Lib/Fluxor/Persistence/StoreRehydratingAction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fluxor.Persistence +{ + /// + /// Dispatched by the store once it has started rehydrating from state. + /// + public sealed class StoreRehydratingAction + { + } +} diff --git a/Source/Lib/Fluxor/Store.cs b/Source/Lib/Fluxor/Store.cs index 56943278..355819e1 100644 --- a/Source/Lib/Fluxor/Store.cs +++ b/Source/Lib/Fluxor/Store.cs @@ -1,7 +1,13 @@ -using System; +#nullable enable +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +#if NET6_0_OR_GREATER +using System.Text.Json; +using System.Text.Json.Nodes; +using Fluxor.Persistence; +#endif using System.Threading.Tasks; namespace Fluxor @@ -14,6 +20,10 @@ public class Store : IStore, IActionSubscriber, IDisposable /// public Task Initialized => InitializedCompletionSource.Task; +#if NET6_0_OR_GREATER + private readonly IPersistenceManager? _persistenceManager; +#endif + private object SyncRoot = new object(); private bool Disposed; private readonly IDispatcher Dispatcher; @@ -30,16 +40,35 @@ public class Store : IStore, IActionSubscriber, IDisposable private volatile bool HasActivatedStore; private bool IsInsideMiddlewareChange => BeginMiddlewareChangeCount > 0; +#if NET6_0_OR_GREATER + + /// + /// Creates an instance of the store + /// + public Store(IDispatcher dispatcher, IPersistenceManager persistenceManager = null) + { + + _persistenceManager = persistenceManager; + ActionSubscriber = new ActionSubscriber(); + Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + Dispatcher.ActionDispatched += ActionDispatched; + Dispatcher.Dispatch(new StoreInitializedAction()); + } + +#else + /// /// Creates an instance of the store /// public Store(IDispatcher dispatcher) { + ActionSubscriber = new ActionSubscriber(); Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); Dispatcher.ActionDispatched += ActionDispatched; Dispatcher.Dispatch(new StoreInitializedAction()); } +#endif /// public IEnumerable GetMiddlewares() => Middlewares; @@ -267,6 +296,15 @@ private async Task ActivateStoreAsync() { HasActivatedStore = true; DequeueActions(); + +#if NET6_0_OR_GREATER + if (_persistenceManager is not null) + { + //Rehydrate as necessary + Dispatcher.Dispatch(new StoreRehydratingAction()); + } +#endif + InitializedCompletionSource.SetResult(true); } } @@ -301,5 +339,42 @@ private void DequeueActions() IsDispatching = false; } } + +#if NET6_0_OR_GREATER + + public string SerializeToJson() + { + var rootObj = new JsonObject(); + foreach (var kv in FeaturesByName) + { + var featureName = kv.Value.GetName(); + var featureValue = kv.Value.GetState(); + rootObj[featureName] = JsonSerializer.SerializeToNode(featureValue); + } + + return JsonSerializer.Serialize(rootObj); + } + + public void RehydrateFromJson(string json) + { + var obj = JsonDocument.Parse(json); + + foreach (var feature in obj.RootElement.EnumerateObject()) + { + //Replace the state in the named feature with what's in the serialized data + if (Features.ContainsKey(feature.Name)) + { + var stateType = Features[feature.Name].GetStateType(); + var featureValue = feature.Value.Deserialize(stateType); + + if (featureValue is null) + continue; + + Features[feature.Name].RestoreState(featureValue); + } + } + } + +#endif } } From 894a244eab871ae2556b6b4887bcfd52867bdad8 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Mon, 11 Mar 2024 01:09:54 -0500 Subject: [PATCH 6/7] Added a few missing items --- .../Components/FluxorComponent.cs | 15 ++--- Source/Lib/Fluxor/IStore.cs | 13 +++++ .../Fluxor/Persistence/PersistenceEffects.cs | 56 +++++++++++++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 Source/Lib/Fluxor/Persistence/PersistenceEffects.cs diff --git a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs index bc89314a..34e78ba3 100644 --- a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs +++ b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components; using System.Threading; using System; +using System.Threading.Tasks; namespace Fluxor.Blazor.Web.Components { @@ -65,15 +66,15 @@ public void Dispose() } } - /// - /// Initializes the store (primarily intended for WASM components and pages). - /// - protected override void async Task OnParametersSetAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnParametersSetAsync(); + if (firstRender) + { + //Attempt to initialize the store knowing that if it's already been initialized, this won't do anything. + await Store.InitializeAsync(); + } - //Attempt to initialize the store knowing that if it's already been initialized, this won't do anything - await Store.InitializeAsync(); + await base.OnAfterRenderAsync(firstRender); } /// diff --git a/Source/Lib/Fluxor/IStore.cs b/Source/Lib/Fluxor/IStore.cs index 03b1cde5..03913a21 100644 --- a/Source/Lib/Fluxor/IStore.cs +++ b/Source/Lib/Fluxor/IStore.cs @@ -93,5 +93,18 @@ public interface IStore : IActionSubscriber /// Executed when an exception is not handled /// event EventHandler UnhandledException; + + /// + /// Persists the features in the store to a serialized string. + /// + /// + string SerializeToJson(); + + /// + /// Deserializes a previously serialized store and rehydrates each feature using the + /// provided values. + /// + /// The serialized store. + void RehydrateFromJson(string json); } } diff --git a/Source/Lib/Fluxor/Persistence/PersistenceEffects.cs b/Source/Lib/Fluxor/Persistence/PersistenceEffects.cs new file mode 100644 index 00000000..bfd24a92 --- /dev/null +++ b/Source/Lib/Fluxor/Persistence/PersistenceEffects.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Fluxor.Persistence +{ + public sealed class PersistenceEffects + { + private readonly IPersistenceManager _persistenceManager; + private readonly IServiceProvider _serviceProvider; + + public PersistenceEffects(IPersistenceManager persistenceManager, IServiceProvider serviceProvider) + { + _persistenceManager = persistenceManager; + _serviceProvider = serviceProvider; + } + + /// + /// Maintains a reference to IStore - injected this way to avoid a circular dependency during the effect method registration + /// + private readonly Lazy _store = new(_serviceProvider.GetRequiredService); + + [EffectMethod(typeof(StorePersistingAction))] + public async Task PersistStoreData(IDispatcher dispatcher) + { + //Serialize the store + var json = _store.Value.SerializeToJson(); + + //Save to the persistence manager + await _persistenceManager.PersistStoreToStateAsync(json); + + //Completed + dispatcher.Dispatch(new StorePersistedAction()); + } + + [EffectMethod(typeof(StoreRehydratingAction))] + public async Task RehydrateStoreData(IDispatcher dispatcher) + { + //Read from the persistence manager + var serializedStore = await _persistenceManager.RehydrateStoreFromStateAsync(); + if (serializedStore is null) + { + //Nothing to rehydrate - leave as-is + dispatcher.Dispatch(new StoreRehydratedAction()); + return; + } + + _store.Value.RehydrateFromJson(serializedStore); + + //Completed + dispatcher.Dispatch(new StoreRehydratedAction()); + } + } +} From 0f337a987c93819ee3c7ff0200982b9a961877a4 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Mon, 1 Apr 2024 20:45:08 -0500 Subject: [PATCH 7/7] Updated to resolve flicker --- .../Components/FluxorComponent.cs | 28 +++++++++---------- Source/Lib/Fluxor/Store.cs | 15 +++++++--- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs index 34e78ba3..24544b61 100644 --- a/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs +++ b/Source/Lib/Fluxor.Blazor.Web/Components/FluxorComponent.cs @@ -15,6 +15,9 @@ public abstract class FluxorComponent : ComponentBase, IDisposable [Inject] private IActionSubscriber ActionSubscriber { get; set; } + [Inject] + private IStore Store { get; set; } + private bool Disposed; private IDisposable StateSubscription; private readonly ThrottledInvoker StateHasChangedThrottler; @@ -22,7 +25,7 @@ public abstract class FluxorComponent : ComponentBase, IDisposable /// /// Creates a new instance /// - public FluxorComponent() + protected FluxorComponent() { StateHasChangedThrottler = new ThrottledInvoker(() => { @@ -65,28 +68,25 @@ public void Dispose() Disposed = true; } } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - //Attempt to initialize the store knowing that if it's already been initialized, this won't do anything. - await Store.InitializeAsync(); - } - - await base.OnAfterRenderAsync(firstRender); - } - + /// /// Subscribes to state properties /// protected override void OnInitialized() { - base.OnInitialized(); StateSubscription = StateSubscriber.Subscribe(this, _ => { StateHasChangedThrottler.Invoke(MaximumStateChangedNotificationsPerSecond); }); + + base.OnInitialized(); + } + + protected override async Task OnInitializedAsync() + { + //Attempt to initialize the store knowing that if it's already been initialized, this won't do anything. + await Store.InitializeAsync(); + await base.OnInitializedAsync(); } protected virtual void Dispose(bool disposing) diff --git a/Source/Lib/Fluxor/Store.cs b/Source/Lib/Fluxor/Store.cs index 355819e1..476969a7 100644 --- a/Source/Lib/Fluxor/Store.cs +++ b/Source/Lib/Fluxor/Store.cs @@ -1,4 +1,5 @@ #nullable enable +using Fluxor.Persistence; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -294,9 +295,6 @@ private async Task ActivateStoreAsync() lock (SyncRoot) { - HasActivatedStore = true; - DequeueActions(); - #if NET6_0_OR_GREATER if (_persistenceManager is not null) { @@ -304,6 +302,8 @@ private async Task ActivateStoreAsync() Dispatcher.Dispatch(new StoreRehydratingAction()); } #endif + HasActivatedStore = true; + DequeueActions(); InitializedCompletionSource.SetResult(true); } @@ -317,6 +317,13 @@ private void DequeueActions() IsDispatching = true; try { + //Only persist the store state if the action(s) in the queue don't consist solely of any combination of the following + if (!QueuedActions.IsEmpty && !QueuedActions.All(action => action is StoreInitializedAction or StoreRehydratingAction or StoreRehydratedAction or StorePersistingAction or StorePersistedAction)) + { + //Add an action to the end of the queue that persists the results of the actions to state, skipping the dispatcher approach (which might lead to an infinite loop) + QueuedActions.Enqueue(new StorePersistingAction()); + } + while (QueuedActions.TryDequeue(out object nextActionToProcess)) { // Only process the action if no middleware vetos it @@ -370,7 +377,7 @@ public void RehydrateFromJson(string json) if (featureValue is null) continue; - Features[feature.Name].RestoreState(featureValue); + FeaturesByName[feature.Name].RestoreState(featureValue); } } }