6
votes

I have a component using an Blazor InputFile component as a sub-component.

When I select a file the OnChange handler is called as expected. However, if I select the same file twice the OnChange handler is not called again (which I guess is as intended since the selection did not change, however my use case needs this).

So, I figure if I can select a file and get a call to the OnChange handler and in the OnChange handler "reset" the selected file, then I should get a new call to the handler even if the same file is selected again.

I cant figure out how to reset the file selection in InputFile (sub)component. Calling this.StateHasChanged() in the handler doesn't cause the InputFile component to re-render.

Is this possible to do without JSInterop and manually setting the value-field of the DOM input element to "" (would that even work)?

My component:

@using stuff;

<div class="drag-drop-area">
    Drag and drop file here
    <InputFile OnChange="@OnInputFileChange"></InputFile>
</div>

@code {

    [Parameter]
    public String SomeParam { get; set; } = "";

    private async Task OnInputFileChange(InputFileChangeEventArgs e) {
        // do stuff with file

        // do _something_ here to reset InputFile

        this.StateHasChanged(); //<-- this doesn't cause InputFile re-render
    }

My attempts to do this so far includes:

  • Following various tips/tricks related to this. StateHasChanged(), i.e.
    • await Task.Delay(1);
    • await InvokeAsync(StateHasChanged);
  • Adding values to InputFile using AdditionalAttributes.Add(..) to see if that could force a re-render
  • Looked at dynamically adding the InputFile component using RenderFragment, but I cant pass the OnChanged param (handler) when creating a new instance of InputFile in code, which means I wont the a callback with InputFileChangeEventArgs
  • Looked at wrapping the InputFile in a EditForm to reset the form (Blazor EditForms apparently doesnt have reset functionality?)
  • [EDIT] Used both Task and void for OnInputFileChange
3

3 Answers

2
votes

Still not a very nice solution - but a bit more concise and it works:

Wrap the InputFile inside a boolean to temporarily hide/show. This clears the value.

  @if (!bClearInputFile)
   {
       <InputFile class="form-control-file" OnChange="@OnInputFileChange" />
   }

   @code
   {
        //Call ClearInputFile whenever value must be cleared.
        private void ClearInputFile()
        {
            bClearInputFile = true;
            StateHasChanged();
            bClearInputFile = false;
            StateHasChanged();
        }
   }
0
votes

Try using a conditional statement and render identical content under both conditions. Changing the conditional should force an update.

@if (@reviewMechanism == "IMPORT")
{
    <div>
        <u>Import</u>
        <br />
        <br />
        <div>

            <div class="btn  btn-sm" style="background-color: lightgray; margin-bottom: 5px; width: 250px; margin-left: 0px ">
                <span>
                    <button class="btn  btn-sm" style=" font: smaller; border: solid; border-color: gray; border-width: thin;  background-color: rgba(239, 239, 239, 1.00);  margin-left: 0px"
                            @onclick="DownloadTemplate">
                        Download
                    </button>
                    ReviewTemplate.csv
                </span>
            </div>
            <br />
            <div class="btn  btn-sm" style="font: smaller;  margin-bottom: 5px;  width: 250px ; background-color: lightgray;height: 40px">
                <InputFile OnChange="@LoadFiles" style=" margin-left: 10px"> Pick a File </InputFile>
            </div>
        </div>


    </div>
}
else if (@reviewMechanism == "IMPORT2")
{
    <div>
        <u>Import</u>
        <br />
        <br />
        <div>

            <div class="btn  btn-sm" style="background-color: lightgray; margin-bottom: 5px; width: 250px; margin-left: 0px ">
                <span>
                    <button class="btn  btn-sm" style=" font: smaller; border: solid; border-color: gray; border-width: thin;  background-color: rgba(239, 239, 239, 1.00);  margin-left: 0px"
                            @onclick="DownloadTemplate">
                        Download
                    </button>
                    ReviewTemplate.csv
                </span>
            </div>
            <br />
            <div class="btn  btn-sm" style="font: smaller;  margin-bottom: 5px;  width: 250px ; background-color: lightgray;height: 40px">
                <InputFile OnChange="@LoadFiles" style=" margin-left: 10px"> Pick a File </InputFile>
            </div>
        </div>


    </div>
}

When change desired:

    loadedFiles = new Dictionary<IBrowserFile, string>();


    if (reviewMechanism == "IMPORT")
    {
        reviewMechanism = "IMPORT2"; //force render
    }
    else
    {
        if (reviewMechanism == "IMPORT2") reviewMechanism = "IMPORT"; //force render
    }
0
votes

A little late to the party but I ran into a similar problem as the the OP with a slightly different use case. But either way the solution seems to be straight forward and would probably work. Just use some old fashioned javascript. So to reset the input element in the OnChange method, it would look like this:

    [Inject]
    private IJSRuntime _js { get; set; }
    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        IBrowserFile file = e.File;
        if (!ValidateFile(file))
        {
            //clearInput is the name of the javascript function
            //ref-upload is the id given to the InputFile element
            await _js.InvokeVoidAsync("clearInput", "ref-upload");
            return;
        }

    }

Then drop the following javascript function somewhere on your site like site.js:

function clearInput(inputId) {
    setTimeout(function () {
        var input = document.querySelector("#" + inputId);
        if (input) {
            input.value = "";
        }
    }, 30);
}