Description
Is your feature request related to a problem? Please describe.
I'd like to be able to undo invalid user input without changing the state of the component or using the bind:value
Blazor data binding feature.
I'm trying to do that inside oninput
event handler like below (online repl).
I've found that ChangeEventArgs.Value
, while being a get/set property, only works one way, from JS to DotNet. More so, StateHasChanged
doesn't trigger rerendering in this case. Thus, I had to resort to JS interop to revert the changes inside the <input>
box A.
(To be clear, I realize this is a horrible hack; it could possible be improved by using ElementReference
and an isolated JS import, but it all wouldn't be necessary if we could just do e.Value = count
below).
<label>Count:</label>
<button @onclick=Increment>@count times!</button><br>
A: <input @oninput=OnChange value="@count" id="@id"><br>
B: <input @bind-value=count @bind-value:event="oninput">
@code {
int count = 1;
string id = Guid.NewGuid().ToString("N");
void Increment() => count++;
void OnChange(ChangeEventArgs e)
{
var userValue = e.Value?.ToString();
if (int.TryParse(userValue, out var v))
{
count = v;
}
else
{
if (String.IsNullOrWhiteSpace(userValue))
{
count = 0;
}
// this doesn't work
e.Value = count.ToString();
// this doesn't work either (no rererendering):
StateHasChanged();
}
}
}
Describe the solution you'd like
Make ChangeEventArgs.Value
work both ways for the scope of the event handler. This would allow for custom input validation logic outside standard bind-xxx
Blazor data binding attributes.
For example, I can do this in Svelte (which I find conceptually very close to Blazor):
<script>
let count = 1;
const handleClick = () => count++;
const handleChange = e => {
const userValue = e.target.value;
let newValue = userValue? parseInt(userValue): 0;
if (isNaN(newValue)) newValue = count;
if (newValue === count)
e.target.value = count; // undo user input
else
count = newValue;
};
</script>
Count: <button on:click={handleClick}>{count}</button><br/>
A: <input value={count} on:input={handleChange}/><br/>
And here it is in React:
function App() {
let [count, setCount] = useState(1);
const handleClick = () => setCount((count) => count + 1);
const handleChange = (e) => {
const userValue = e.target.value;
let newValue = userValue ? parseInt(userValue) : 0;
if (isNaN(newValue)) newValue = count;
// re-render even when count hasn't changed
setCount(newValue);
};
return (
<>
Count: <button onClick={handleClick}>{count}</button><br/>
A: <input value={count} onInput={handleChange}/><br/>
</>
);
}
Additional context
https://stackoverflow.com/q/70016908/1768303
To clarify, I simply want to undo whatever I consider an invalid input, retrospectively after it has happened (by handling the change event), without mutating the component's state itself (counter
here).
That is, without Blazor-specific two-way data binding, HTML native type=number
or pattern matching attributes. I use the number format requirement here as an example; I want to be able to undo any arbitrary input like that.
I might be missing something, but I find this surprisingly difficult to do in Blazor, compared to other frameworks. I'd like to be able to use StateHasChanged
to simply re-render the component in its current state.