skip to Main Content

I am creating a Blazor webassembly application and ran into an issue when looping over content to generate urls to mp3 files.

The media files are called "1.mp3", "2.mp3", "3.mp3" … etc.

Assume you have a razor page with this content:

@* set counter variable to 1 *@
@InitCounter()

@foreach(string str in strings)
{
   <div>
       <span @onclick="() => PlayUrl(counter)">@counter</span>
       <span>@str</span>
   </div>

   @* Increment counter variable by 1 *@
   @IncrementCounter()
}

@code
{
    public int counter = 1;

    public string InitCounter()
    {
       counter = 1;
       return string.Empty;
    }

    public string IncrementCounter()
    {
       counter++;
       return string.Empty;
    }
}

I have troubles understanding why the function PlayUrl is being called with the wrong value, when I click on that particular span element in the browser?

Lets says that strings is defined this way:


   strings[] strings = { "aaa", "bbb" };

This should generate some html similar to this:

<div>
  <span b-2e01zi41cp="">1</span>
  <span b-2e01zi41cp="">aaa</span>
</div>
<div>
  <span b-2e01zi41cp="">2</span>
  <span b-2e01zi41cp="">bbb</span>
</div>

No matter which span element that I click on, it will always call PlayUrl with the argument ‘3’.

Is there anyone that can explain why and how to make Blazor pass the right value to PlayUrl?

2

Answers


  1. You have turned a foreach() into a for() loop, and for-loops and lambda functions are a dangerous combination.

    @foreach(string str in strings)
    {
       int counterCopy = counter;
       <div>
           <span @onclick="() => PlayUrl(counterCopy)">@counter</span>
           <span>@str</span>
       </div>
    
       @* Increment counter variable by 1 *@
       @IncrementCounter()
    }
    

    for an explanation, look up one of the countless duplicates, search term "closing over the loop variable"

    Login or Signup to reply.
  2. @foreach (string str in strings)
    {
       var localCounter = counter; // Capture the current value of counter
       <div>
           <span @onclick="() => PlayUrl(localCounter)">@counter</span>
           <span>@str</span>
       </div>
    
       @IncrementCounter()
    }
    

    In Blazor, when you use a lambda expression (() => PlayUrl(counter)) inside a loop, it captures the variable counter by reference, not by value. This means that every lambda expression in your loop is referring to the same counter variable, which is why PlayUrl always receives the final value of counter after the loop has completed.

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