I`m using Fluxor for state management for my NET8 Blazor WASM app. My parent component passes down state from the store to the child component via an input parameter.
parent
@page "/people"
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState<PeopleState> _state
<ShowPeople Requests="_state.Value.People.ToArray()"></ShowPeople>
child
@foreach(var person in People)
{
<div>@person.Name</div>
}
@code {
[Parameter] public Person[] People { get; set; } = [];
}
state excerpt
public record PeopleState
{
public bool IsLoading { get; init; }
public bool IsLoaded { get; init; }
public string? Error { get; init; }
public DateTimeOffset LoadedAt { get; init; }
public List<Person> People { get; init; } = [];
}
public class PeopleFeatureState : Feature<PeopleState>
{
public override string GetName() => nameof(PeopleState);
protected override PeopleState GetInitialState() => new();
}
public record RejectPerson(string Id);
public record RejectPersonSuccess(string Id);
public record RejectPersonFailure(string Error);
//...
public static class PeopleReducer
{
[ReducerMethod] public static PeopleState HandleRejectPerson(PeopleState state, RejectPerson _)
{
return state with { IsLoading = true };
}
[ReducerMethod] public static PeopleState HandleRejectPersonFailure(PeopleState state, RejectPersonFailure action)
{
return state with
{
IsLoading = false,
Error = action.Error
};
}
[ReducerMethod] public static PeopleState HandleRejectPersonSuccess(PeopleState state, RejectPersonSuccess action)
{
return state with
{
IsLoading = false,
People = [.. state.People.Where(u => u.Id != action.Id)] //remove person
};
}
//...
}
public class PeopleEffects(IState<PeopleState> state)
{
[EffectMethod] public async Task OnRejectPerson(RejectPerson action, IDispatcher dispatcher)
{
string id = action.Id;
try
{
var http = new HttpClient();
HttpResponseMessage resp = await http.DeleteAsync($"people/{id}");
if (!resp.IsSuccessStatusCode)
{
throw new ApplicationException("error");
}
dispatcher.Dispatch(new RejectPersonSuccess(id));
}
catch (Exception ex)
{
dispatcher.Dispatch(new RejectPersonFailure(ex.Message));
}
}
//...
}
The state is a list of people that can be approved or rejected (user signups). When a RejectPerson
action is dispatched, I can see it happening in the Redux DevTools and there the state also updates. But in the UI the child component does not change.
I also tried forcing a refresh when explicitly listening the relevant event, but it also didn’t work.
protected override void OnInitialized()
{
base.OnInitialized();
ActionSubscriber.SubscribeToAction<RejectPersonSuccess>(this, (action) => StateHasChanged());
}
Though I know, I can just inject the state in the child and this works, I need to understand why it doesnt work when using an input parameter. Any ideas?
Also what completely puzzles me, is that an action triggering the following reducer correctly updates the state and UI. LoadPeopleStateSuccess does a round-trip to the database and gets all data. Basically the same as is done when the parent is loaded.
public record LoadPeopleState(bool Force = false);
public record LoadPeopleStateSuccess(Person[] People);
[ReducerMethod] public static PeopleState HandleLoadPeopleStateSuccess(PeopleState state, LoadPeopleStateSuccess action)
{
return state with
{
IsLoading = false,
IsLoaded = true,
People = [.. action.People],
LoadedAt = DateTimeOffset.UtcNow
};
}