skip to Main Content

The following code snippet does exactly what I expect, which is to provide a list of 3 items that expands to show the numeral for the written number when clicked. My question is whether a Dictionary is the proper way to track the state?

Do I really have to maintain a copy of the state on this page, or is there a way to bind the click event to directly modify the value of the property on the component rather than a local copy of the status which is bound to the components value?

<div class="row">
    <div class="col">
        <ul>
            @if(Items != null)
            {
                foreach(var kvp in Items)
                {
                    <li @key="kvp.Key" @onclick="@(e => Toggle(kvp.Key))">@kvp.Key <CollapsibleRow @bind-Expanded="expanded[kvp.Key]">@kvp.Value</CollapsibleRow></li>
                }
            }
        </ul>
    </div>
</div>

@code {
    Dictionary<string, bool> expanded = Items.Select(o => new KeyValuePair<string, bool>(o.Key, false)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

    void Toggle(string key) 
    {
        
        expanded[key] = !expanded[key]; 
    }

    static Dictionary<string, string> Items = new()
    {
        { "One", "1" },
        { "Two", "2" },
        { "Three", "3" }
    };
}

3

Answers


  1. Chosen as BEST ANSWER

    Here is the code I ended up with that gets rid of the "middle man", so to speak, so that I can directly call the Toggle method on the component rather than having to rely on a property binding. Although not shown here, this also reduces the amount of code necessary in the component itself now that it doesn't need to expose a parameter.

    <div class="row">
        <div class="col">
            <ul>
                @if(Items != null)
                {
                    foreach(var kvp in Items)
                    {
                        <li @key="kvp.Key" @onclick="@(e => collapsibleRows[kvp.Key].Toggle())">
                            @kvp.Key 
                            <CollapsibleRow @ref="collapsibleRows[kvp.Key]">@kvp.Value</CollapsibleRow>
                        </li>
                    }
                }
            </ul>
        </div>
    </div>
    
    @code {
        Dictionary<string, CollapsibleRow> collapsibleRows = new();
    
        static Dictionary<string, string> Items = new()
        {
            { "One", "1" },
            { "Two", "2" },
            { "Three", "3" }
        };
    }
    

  2. In this case I would move away from the Dictionary, and use a view model instead, so that the expanded tracking is held with the object itself. I may have mixed up where you have used the number and the name of the number, but hopefully you get the idea.

    <div class="row">
        <div class="col">
            <ul>
                @if(Items != null)
                {
                    foreach(NumberVm item in Items)
                    {
                        <li @onclick="@(() => Toggle(item))">
                            @item.Name <CollapsibleRow @bind-Expanded="@item.Expanded">@item.Number</CollapsibleRow>
                        </li>
                    }
                }
            </ul>
        </div>
    </div>
    
    @code {
        List<NumberVm> NumbersList = new()
        {
            new NumberVm{ Number = 1, Name = "One" },
            new NumberVm{ Number = 2, Name = "Two" },
            new NumberVm{ Number = 3, Name = "Three" },
        }
    
        void Toggle(NumberItem item) 
        {
            item = !item;
        }
    
        private class NumberVm
        {
            public int Number { get; set; }
            public string Name { get; set; }
            public bool Expanded { get; set; }
        }
    }
    
    Login or Signup to reply.
  3. You need to maintain state if you wish to preserve the state across page changes. If not, you can maintain state internally.

    Here’s a Bootstrap accordion version of your code where the state is maintained by each AccordionItem.

    Accordion

    <div class="accordian">
        @ChildContent
    </div>
    
    @code {
        [Parameter] public RenderFragment? ChildContent { get; set; }
    }
    

    AccordionItem

    <div class="accordion-item">
        <h2 class="accordion-header">
            <button class="@_headercss" type="button" @onclick="OnChangeState">
                @this.Header
            </button>
        </h2>
        <div class="@_bodycss">
            <div class="accordion-body">
              @ChildContent 
            </div>
        </div>
    </div>
    
    @code {
        [Parameter] public RenderFragment? ChildContent { get; set; }
        [Parameter] public string? Header { get; set; }
        [Parameter] public bool IsInitiallyOpen { get; set; }
    
        private bool _open;
        private string _headercss => _open ? "accordion-button" : "accordion-button collapsed";
        private string _bodycss => _open ? "accordion-collapse collapse show" : "accordion-collapse collapse";
    
        protected override void OnInitialized()
        {
            _open = this.IsInitiallyOpen;
        }
    
        private void OnChangeState()
        {
            _open = !_open;
        }
    }
    

    Home

    @page "/"
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    <Accordion>
        @foreach(var kvp in Items)
        {
            <AccordionItem Header="@kvp.Key">@kvp.Value</AccordionItem>
        }
    </Accordion>
    
    @code {
        static Dictionary<string, string> Items = new()
        {
            { "One", "1" },
            { "Two", "2" },
            { "Three", "3" }
        };
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search