Skip to content

Commit

Permalink
IStateSelector<TState, TValue> #221 (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrpmorris authored Nov 25, 2021
1 parent a252915 commit 8933468
Show file tree
Hide file tree
Showing 30 changed files with 397 additions and 182 deletions.
2 changes: 1 addition & 1 deletion Docs/disposable-callback-not-disposed.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# DisposableCallback was not disposed

Components that descend from `FluxorComponent` or `FluxorLayout` automatically subscribe to the
`StateChanged` event on every `IState<T>` property in the component automatically. When the component
`StateChanged` event on every `IState<TState>` and `IStateSelection<TState, TValue>` property in the component automatically. When the component
is disposed, this subscription is removed, to avoid memory leaks.

If ever you see an error message like the following
Expand Down
1 change: 1 addition & 0 deletions Docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## New in 5.0
* Removed need to reference `_content/Fluxor.Blazor.Web/scripts/index.js` ([#235](https://github.com/mrpmorris/Fluxor/issues/235))
* Separated `IDispatcher` out of `IStore`. ([#209](https://github.com/mrpmorris/Fluxor/issues/209))
* Added `IState<TState>` alternative `IStateSelector<TState, TValue>` for selecting and subscribing to subsets of state. ([#221](https://github.com/mrpmorris/Fluxor/issues/221))

## New in 4.2.1
* Support .NET 6
Expand Down
3 changes: 2 additions & 1 deletion Source/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<LangVersion>9</LangVersion>
</PropertyGroup>

<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<NoWarn>NU5118</NoWarn>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@

</PropertyGroup>

<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.1.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1.8" />
Expand Down
2 changes: 1 addition & 1 deletion Source/Fluxor.Blazor.Web/Components/FluxorComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Fluxor.Blazor.Web.Components
{
/// <summary>
/// A component that auto-subscribes to state changes on all <see cref="IState"/> properties
/// A component that auto-subscribes to state changes on all <see cref="IStateChangedNotifier"/> properties
/// and ensures <see cref="ComponentBase.StateHasChanged"/> is called
/// </summary>
public abstract class FluxorComponent : ComponentBase, IDisposable
Expand Down
2 changes: 1 addition & 1 deletion Source/Fluxor.Blazor.Web/Components/FluxorLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Fluxor.Blazor.Web.Components
{
/// <summary>
/// A layout that auto-subscribes to state changes on all <see cref="IState"/> properties
/// A layout that auto-subscribes to state changes on all <see cref="IStateChangedNotifier"/> properties
/// and ensures <see cref="LayoutComponentBase.StateHasChanged"/> is called
/// </summary>
public abstract class FluxorLayout : LayoutComponentBase, IDisposable
Expand Down
2 changes: 1 addition & 1 deletion Source/Fluxor.Blazor.Web/Middlewares/Routing/Effects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public Effects(NavigationManager navigationManager)
}

[EffectMethod]
public Task HandleGoActionAsync(GoAction action, IDispatcher dispatcher)
public Task HandleGoActionAsync(GoAction action, IDispatcher _)
{
Uri fullUri = NavigationManager.ToAbsoluteUri(action.NewUri);
if (fullUri.ToString() != NavigationManager.Uri || action.ForceLoad)
Expand Down
12 changes: 6 additions & 6 deletions Source/Fluxor.Blazor.Web/StoreInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,23 @@ protected virtual void Dispose(bool disposing)
Store.UnhandledException -= OnUnhandledException;
}

private void OnUnhandledException(object sender, Exceptions.UnhandledExceptionEventArgs args)
private void OnUnhandledException(object sender, Exceptions.UnhandledExceptionEventArgs e)
{
InvokeAsync(async () =>
{
Exception exceptionThrownInHandler = null;
try
{
await UnhandledException.InvokeAsync(args).ConfigureAwait(false);
await UnhandledException.InvokeAsync(e).ConfigureAwait(false);
}
catch (Exception e)
catch (Exception exception)
{
exceptionThrownInHandler = e;
exceptionThrownInHandler = exception;
}

if (exceptionThrownInHandler != null || !args.WasHandled)
if (exceptionThrownInHandler != null || !e.WasHandled)
{
ExceptionToThrow = exceptionThrownInHandler ?? args.Exception;
ExceptionToThrow = exceptionThrownInHandler ?? e.Exception;
StateHasChanged();
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static IServiceCollection AddFluxor(
typesToScan: options.TypesToScan,
scanIncludeList: scanIncludeList);
services.AddScoped(typeof(IState<>), typeof(State<>));
services.AddScoped(typeof(IStateSelection<,>), typeof(StateSelection<,>));

return services;
}
Expand Down
32 changes: 7 additions & 25 deletions Source/Fluxor/Feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public abstract class Feature<TState> : IFeature<TState>
/// <summary>
/// A list of reducers registered with this feature
/// </summary>
protected readonly List<IReducer<TState>> Reducers = new List<IReducer<TState>>();
protected readonly List<IReducer<TState>> Reducers = new();

private bool HasInitialState;
private SpinLock SpinLock = new SpinLock();
private SpinLock SpinLock = new();
private readonly ThrottledInvoker TriggerStateChangedCallbacksThrottler;

/// <summary>
Expand All @@ -48,34 +48,17 @@ public Feature()
TriggerStateChangedCallbacksThrottler = new ThrottledInvoker(TriggerStateChangedCallbacks);
}

private EventHandler untypedStateChanged;
event EventHandler IFeature.StateChanged
private EventHandler _stateChanged;
public event EventHandler StateChanged
{
add
{
SpinLock.ExecuteLocked(() => untypedStateChanged += value );
SpinLock.ExecuteLocked(() => _stateChanged += value );
}

remove
{
SpinLock.ExecuteLocked(() => untypedStateChanged -= value);
}
}

private EventHandler<TState> stateChanged;
/// <summary>
/// Event that is executed whenever the state changes
/// </summary>
public event EventHandler<TState> StateChanged
{
add
{
SpinLock.ExecuteLocked(() => stateChanged += value);
}

remove
{
SpinLock.ExecuteLocked(() => stateChanged -= value);
SpinLock.ExecuteLocked(() => _stateChanged -= value);
}
}

Expand Down Expand Up @@ -134,8 +117,7 @@ public virtual void ReceiveDispatchNotificationFromStore(object action)

private void TriggerStateChangedCallbacks()
{
stateChanged?.Invoke(this, State);
untypedStateChanged?.Invoke(this, EventArgs.Empty);
_stateChanged?.Invoke(this, EventArgs.Empty);
}
}
}
5 changes: 0 additions & 5 deletions Source/Fluxor/IFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,5 @@ public interface IFeature<TState> : IFeature
/// <param name="reducer">The reducer instance</param>
/// <seealso cref="DependencyInjection.Options.UseDependencyInjection(System.Reflection.Assembly[])"/>
void AddReducer(IReducer<TState> reducer);

/// <summary>
/// Event that is executed whenever the state changes
/// </summary>
new event EventHandler<TState> StateChanged;
}
}
24 changes: 3 additions & 21 deletions Source/Fluxor/IState.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,15 @@
using System;

namespace Fluxor
namespace Fluxor
{
/// <summary>
/// An interface that is injected into Blazor Components / pages for accessing
/// the state of an <see cref="IFeature{TState}"/>
/// </summary>
public interface IState
{
/// <summary>
/// Event that is executed whenever the state changes
/// </summary>
event EventHandler StateChanged;
}

/// <summary>
/// An interface that is injected into Blazor Components / pages for accessing
/// the state of an <see cref="IFeature{TState}"/>
/// </summary>
/// <typeparam name="TState">The type of the state</typeparam>
public interface IState<TState> : IState
public interface IState<TState> : IStateChangedNotifier
{
/// <summary>
/// Returns the current state of the feature
/// Returns the value selected from the feature state
/// </summary>
TState Value { get; }
/// <summary>
/// Event that is executed whenever the state changes
/// </summary>
new event EventHandler<TState> StateChanged;
}
}
26 changes: 26 additions & 0 deletions Source/Fluxor/IStateSelection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace Fluxor
{
/// <summary>
/// An interface that is injected into Blazor Components / pages for accessing
/// the a subset of state of an <see cref="IFeature{TState}"/>
/// </summary>
/// <typeparam name="TState">The type of the state</typeparam>
/// <typeparam name="TValue">The type of the value selected from <see cref="TState"/></typeparam>
public interface IStateSelection<TState, TValue> : IState<TValue>
{
/// <summary>
/// Identifies the part of the feature state to select
/// </summary>
/// <param name="selector">Function to select a value from the feature state</param>
/// <param name="valueEquals">
/// Optional function used to check if two values are equal.
/// Used to determine if an update to state needs
/// to trigger a <see cref="IStateChangedNotifier.StateChanged"/> event
/// </param>
void Select(
Func<TState, TValue> selector,
Func<TValue, TValue, bool> valueEquals = null);
}
}
12 changes: 12 additions & 0 deletions Source/Fluxor/IStateValueChangedNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Fluxor
{
public interface IStateChangedNotifier
{
/// <summary>
/// Event that is executed whenever the observed value of the state changes
/// </summary>
event EventHandler StateChanged;
}
}
40 changes: 8 additions & 32 deletions Source/Fluxor/State.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,20 @@
using System;

namespace Fluxor
namespace Fluxor
{
/// <summary>
/// A class that is injected into Blazor components/pages that provides access
/// to an <see cref="IFeature{TState}"/> state.
/// </summary>
/// <typeparam name="TState"></typeparam>
public class State<TState> : IState<TState>
public class State<TState> : StateSelection<TState, TState>, IStateSelection<TState, TState>
{
private readonly IFeature<TState> Feature;

/// <summary>
/// Creates an instance of the state holder
/// </summary>
/// <param name="feature">The feature that contains the state</param>
public State(IFeature<TState> feature)
{
Feature = feature;
}

/// <see cref="IState{TState}.Value"/>
public TState Value => Feature.State;

/// <summary>
/// Event that is executed whenever the state changes
/// </summary>
public event EventHandler<TState> StateChanged
public State(IFeature<TState> feature) : base(feature)
{
add { Feature.StateChanged += value; }
remove { Feature.StateChanged -= value; }
Select(
x => x, // Select the state itself
valueEquals: DefaultObjectReferenceEquals); // Compare by object reference
}

/// <summary>
/// Event that is executed whenever the state changes
/// </summary>
event EventHandler IState.StateChanged
{
add { (Feature as IFeature).StateChanged += value; }
remove { (Feature as IFeature).StateChanged -= value; }
}
private static bool DefaultObjectReferenceEquals(TState x, TState y) =>
object.ReferenceEquals(x, y);
}
}
Loading

0 comments on commit 8933468

Please sign in to comment.