Search code examples
blazor.net-7.0fluxor

Blazor - Fluxor - State not showing update


I'm new to Blazor and Fluxor. I'm having this issue: when I'm updating the state in one component, the updated state is not shown in a different component.

Actually, the initial state is an empty list. When I debug the reducer, the state is being updated correctly. I can see this, when I change the state the second time, I can see that the state is filled with the former action. But in a different component, the state seems to be empty still. I'm sure I miss something.

This is my component ComboboxFilterOption.razor where the state is being updated:

<div class="card p-2 @IsFilterActiveCssClass">
    <div class="row">
        <div class="col">
            @FilterName
        </div>
    </div>
    <div class="row">
        <div class="col">
            <select class="form-control"
            @onchange="OnChange"
        >
                <option value="-1">Please select</option>
                @if (ComboboxItems != null)
                {
                    @foreach (var comboboxItem in ComboboxItems)
                    {
                        <option [email protected]>@comboboxItem.DisplayValue</option>
                    }
                }
            </select>
        </div>
    </div>
    @if (FilterValue != null)
    {
        <div class="row">
            <div class="col">
                Selected value: @FilterValue
            </div>
        </div>
    }
</div>

This is the code-behind file of ComboboxFilterOption.razor

using Fluxor;
using GUI.Data;
using GUI.Store.Actions;
using GUI.Store.FilterUseCase;
using Microsoft.AspNetCore.Components;

namespace GUI.Shared.Components.Filter
{
    public partial class ComboboxFilterOption
    {
        [Inject]
        private IState<FilterState> FilterState { get; set; }
        [Inject]
        public IDispatcher Dispatcher { get; set; }


        [Parameter] public string FilterName { get; set; } = string.Empty;
        [Parameter] public string InternalFilterName { get; set; } = string.Empty;
        [Parameter] public List<Data.ComboboxItem>? ComboboxItems { get; set; }

        private Enums.FilterType FilterType = Enums.FilterType.Combobox;
        private string? FilterValue;
        private List<Data.FilterOperator>? FilterOperators = Data.FilterOperator.GetOperatorsForType(Enums.FilterType.Combobox);
        private string IsFilterActiveCssClass = string.Empty;

        private void OnChange(ChangeEventArgs e)
        {
            FilterValue = e.Value?.ToString();
            string newFilterActiveValue = string.Empty;
            if (!string.IsNullOrEmpty(FilterValue) && FilterValue != "-1")
            {
                newFilterActiveValue = "bg-success text-white";
            }
            IsFilterActiveCssClass = newFilterActiveValue;
            Data.Filter updatedFilter = new Data.Filter()
            {
                InternalName = InternalFilterName,
                Type = FilterType,
                Operator = new FilterOperator() { Name = "Equals", Number = 1 },
                Value = FilterValue
            };
            var action = new UpdateFilterAction(updatedFilter);
            Dispatcher.Dispatch(action);
        }
    }
}

This is the reducer of FilterState:

using Fluxor;
using GUI.Store.Actions;

namespace GUI.Store.FilterUseCase
{
    public static class Reducers
    {
        [ReducerMethod]
        public static FilterState ReduceUpdateFilterAction(FilterState state, UpdateFilterAction action)
        {
            var alreadySetFilter = state.Filters.Where((filter) => filter.InternalName == action.Filter.InternalName).FirstOrDefault();
            if(alreadySetFilter != null)
            {
                var index = state.Filters.IndexOf(alreadySetFilter);
                state.Filters[index] = action.Filter;
            }
            else
            {
                state.Filters.Add(action.Filter);
            }
            return state;
        }
    }
}

The reducer seems to be working fine, because as described above it is filled, when I call it the second time. Here a screenshot where you can see, that it is filled:

enter image description here

It is also showing in Redux Dev Tools:

enter image description here

Here is the code from the different component (Filter.razor), where I want to show the current FilterState

@using Fluxor;
@using GUI.Store.Actions;
@using GUI.Store.FilterUseCase;
@inherits Fluxor.Blazor.Web.Components.FluxorComponent

<div class="container-fluid">
    <div class="row">
        <div class="col">
            <GUI.Shared.Components.Filter.ComboboxFilterOption
                InternalFilterName="ProductStatus"
                FilterName="Status"
                ComboboxItems=ProductStatus
            />
        </div>
    </div>
    <div>FilterStateCount @FilterState.Value.Filters.Count()</div>
    @foreach (Data.Filter filter in FilterState.Value.Filters)
    {
        <div class="row">
            <div class="col">
                @filter.InternalName: @filter.Value
            </div>
        </div>
    }
</div>

@code {
    [Inject]
    private IState<FilterState> FilterState { get; set; }
    [Inject]
    public IDispatcher Dispatcher { get; set; }

    private List<Data.ComboboxItem> ProductStatus = new List<Data.ComboboxItem>()
    {
        new Data.ComboboxItem() { Value = 1, DisplayValue = "DUMMY DATA - INTRO" },
        new Data.ComboboxItem() { Value = 2, DisplayValue = "DUMMY DATA - ACTIVE" },
        new Data.ComboboxItem() { Value = 3, DisplayValue = "DUMMY DATA - DORMANT" },
        new Data.ComboboxItem() { Value = 4, DisplayValue = "DUMMY DATA - STOP FLAGGED" },
    };
}

I did the Fluxor - Blazor Web Tutorial on https://github.com/mrpmorris/Fluxor/tree/master/Source/Tutorials/02-Blazor/02A-StateActionsReducersTutorial with the Counter example. This example is working fine! So I assume it is not a general issue of the integration of Fluxor.

I actually cannot see why it does not show the current state.


Solution

  • I found the reason. The error was on the reducer. I need to create a new instance of FilterState instead of returning and modifying the old, updated state.

    Before (WRONG!):

    [ReducerMethod]
    public static FilterState ReduceUpdateFilterAction(FilterState state, UpdateFilterAction action)
    {
        var alreadySetFilter = state.Filters.Where((filter) => filter.InternalName == action.Filter.InternalName).FirstOrDefault();
        if(alreadySetFilter != null)
        {
            var index = state.Filters.IndexOf(alreadySetFilter);
            state.Filters[index] = action.Filter;
        }
        else
        {
            state.Filters.Add(action.Filter);
        }
        return state;
    }
    

    CORRECT / WORKING version:

    [ReducerMethod]
    public static FilterState ReduceUpdateFilterAction(FilterState state, UpdateFilterAction action)
    {
        var newList = state.Filters;
        var alreadySetFilter = newList.Where((filter) => filter.InternalName == action.Filter.InternalName).FirstOrDefault();
        if (alreadySetFilter != null)
        {
            var index = newList.IndexOf(alreadySetFilter);
            newList[index] = action.Filter;
        }
        else
        {
            newList.Add(action.Filter);
        }
        return new FilterState(newList);
    }