mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.22k stars 139 forks source link

State doesn't seem to update following effect #413

Closed WhitWaldo closed 1 year ago

WhitWaldo commented 1 year ago

I've got to imagine I'm doing something wrong, but I've got the following in place (modeled after the tutorial repo here)

public abstract record GenericListState<T>
{
  public bool Initialized { get; init; } = false;
  public bool Loading { get; init; } = false;
  public List<T> Items { get; init; } = new();
}

public record Doodad
{
  public string Name { get; init; }
}

public record DoodadListState : GenericListState<Doodad>;

public class DoodadListFeature : Featue<DoodadListState>
{
  public override string GetName() => "ListDoodads"; 
  protected override DoodadListState GetInitialState()
  {
    return new DoodadListState {
      Items = new List<Doodad>(),
      Loading = false,
      Initialized = false
    };
  }  
}

The following actions:

public class DoodadListRetrieveDataAction {}

public class DoodadListRetrieveDataResultAction 
{
  public IEnumerable<Doodad> Doodads { get; }
  public DoodadListRetrieveDataResultAction(IEnumerable<Doodad> doodads)
  {
    Doodads = doodads;
  }
}

The effects:

public class DoodadListEffects
{
  public async Task RetrieveDoodadListAsync(DoodadListRetrieveDataAction _, IDispatcher dispatcher) 
  {
    var doodads = new List<Doodad>
    {
      new Doodad
      {
        Name = "thisthat"
      }
    };

    await Task.Delay(TimeSpan.FromSeconds5));
    dispatcher.Dispatch(new DoodadListRetrieveDataResultAction(doodads));
  }
}

And the reducers:

public static class DoodadListReducers
{
  [ReducerMethod]
  public static DoodadListState ReduceRetrieveDataAction(DoodadListState state, DoodadListRetrieveDataAction _)
  {
    return state with {
      Loading = true,
      Items = new List<Doodad>()
    };
  }

  [ReducerMethod]
  public static DoodadListState ReduceRetrieveDataResultAction(DoodadListState state, DoodadListRetrieveDataResult action action)
  {
    return state with {
      Loading =false,
      Items = action.Doodads.ToList()
    };
  }
}

In my component then:

@inject IDispatcher Dispatcher
@inject IState<TState> ItemListState

@if (ItemListState.Value.Loading)
{
  //Show loading icon
}
else 
{
  //Show the data
}

@code 
{
  protected override void OnInitialized()
  {
    if (!ItemListState.Value.Initialized)
    {
      DIspatcher.Dispatch(new DoodadListRetrieveDataAction());
    }

   base.OnInitialized();
    }
  }
}

Put simply, the loading icon shows up and never goes away. If I set breakpoints, I see each of the reducers fire, but the loading icon never goes away, suggesting that a StateHasChanged() isn't called where it should be. What am I doing wrong here?

mrpmorris commented 1 year ago

Give me a link to a repro and I'll have a look.

WhitWaldo commented 1 year ago

Repro created here. The idea is that after the 5 second timeout in the effect, the component should list all the state instead of the loading text. Instead, I never see anything but the loading text even though the debug shows that both reducers fire as expected.

I appreciate the assist!

WhitWaldo commented 1 year ago

Solved it - as I thought, I missed something quite important and didn't include the @implements FluxorComponent at the top of the component.

mrpmorris commented 1 year ago

That's the usual culprit :)