Blazor InputFile reset/clear value - statehaschanged() not working

8.3k views Asked by At

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
5

There are 5 answers

4
Daniël J.M. Hoffman On BEST ANSWER

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
Online Profile On

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
George Fabish On

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);
}
1
Pankaj Goel On

Below is common and easy solution. Razor Component

<InputFile @key=@(inputFileId) disabled="@DisabledUpload" OnChange="@OnInputFileChange"/>

Added @key

Code section

public Guid inputFileId = Guid.NewGuid();

// Once upload success in OnInputFileChange, Change id so that blazor re-renders InputFile as new component

inputFileId = Guid.NewGuid();
2
James Allderidge On

Instead of calling StateHasChanged twice you can add a @key to the InputFile and change that when processing the OnChange event:

<InputFile @key=@(_inputFileId) OnChange="@LoadFiles" multiple />

@code {
    private string _inputFileId = Guid.NewGuid().ToString();
    
    private Task LoadFiles(InputFileChangeEventArgs e)
    {
        // load file here

        // the InputFile maintains the file the user has chosen and will ignore importing same file, i.e. you can't import the same file more than once to the inputfile control
        // to fix this the InputFile component key is changed so that blazor sees it as a new component and re-creates it in the browser DOM thereby clearing its state (and the file property of it)
        _inputFileId = Guid.NewGuid().ToString();

        return Task.CompletedTask;
    }

}```