Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continuation of PR 40097 (Input components should use SetUpdatesAttributeName) #46434

Merged

Conversation

SteveSandersonMS
Copy link
Member

This is a continuation of #40326. I need to make it into a new PR because the original one is on a branch I can't directly write to. For the details about what this fixes, consider reading the conversations on the earlier PRs and the original issue that led to all this. As a very brief summary:

  • InputText and the other Input* components do not currently use SetUpdatesAttributeName during rendering, so the diffing system doesn't know what text is currently in the DOM, and hence it emits updates to the value if and only if the bound .NET value changes (regardless of what's in the DOM)
  • This leads to a problem if the .NET property setter reverts an incoming change. Doing so means the .NET value is effectively unchanged, and hence the diffing system thinks there's nothing to do, so the DOM value is left alone even though it has changed. The UI is then out of sync with the .NET state.
  • An earlier PR tried to fix this by just adding SetUpdatesAttributeName, but that led to a different problem whereby the Input components lost the ability to represent invalid states (e.g., an InputNumber being blank even though it's bound to an int which cannot have no value).
  • To avoid breaking existing apps and losing the ability to represent invalid states, the newer PR keeps track of the possibly-unparseable incoming values and reflects those back out to the DOM instead of just emitting the latest-validly-parsed model state.

@hakenr, thanks again for contributing this! I know it's take a long time to get this in. Hopefully that doesn't come as a surprise because we previously discussed aiming to do this in an early .NET 8 preview, and that's where we are now.

I think the PR at #40326 is already good and almost ready to merge. However there's a bit of remaining work to rebase on main and a couple of tweaks I want to make the the new public API, so I'll do that directly here if that's OK.

@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Feb 3, 2023
@SteveSandersonMS
Copy link
Member Author

SteveSandersonMS commented Feb 3, 2023

As an open design question, we have to decide where to put the possibly-unparseable incoming value so that subclasses can use it in their rendering logic. I think there are three main options:

  • Most risk-averse: We leave CurrentValueAsString alone (so it continues only to contain the most recent valid value) and introduce a new property LatestUserInputWhichMightNotEvenBeParseable, with some better name. Then we recommend that people subclassing InputBase switch to use LatestUserInputWhichMightNotEvenBeParseable if they can.
  • Middle option: We change CurrentValueAsString to represent the latest user input which may or may not be parseable. But in case someone was relying on getting the last-good-value, we add a new property MostRecentParsedValueAsString so people can switch to that if they need it for back-compat.
  • Most pro-risk: We avoid introducing any new API at all, and instead just change CurrentValueAsString to mean the latest user input, whether or not it was parseable. This is technically a breaking change, but in the vast majority of cases it would improve the behavior of existing InputBase subclasses. In the very rare case where someone really does need the "most recently parsed value as a string", they can use CurrentValue and stringify it themselves.

I was previously advocating a risk-averse approach here, but on further consideration I am now siding with the pro-risk option because I don't want to have multiple different kinds of "current value" that component authors would have to understand and distinguish. It feels better just to make the existing property represent the value people should most likely actually be using. Then we have no new API and existing subclasses will just start behaving better. But we would have to announce it as a breaking change, even if for the vast majority of people no reaction would be required.

@hakenr
Copy link
Member

hakenr commented Feb 3, 2023

@SteveSandersonMS As you are touching the CurrentValueAsString property and thinking about the best design, let me remind you of this issue #44105 that might affect your current design decision. I think we should shape the solution of this issue in a direction that keeps in mind the need for future asynchronous handling of input's onchange event.

…arameter flow like all components should.

Previously this was why setters could successfully revert inputs, but it's not the right way to do it and leads to surprising behavior (like the fact that setters could successfully revert inputs even without using SetUpdatesAttributeName). We explicitly document that components should not mutate their own inputs. Now it doesn't, and behaves more predictably but still passes tests because of SetUpdatesAttributeName.
…* subclasses for the one scenario that needs it
@SteveSandersonMS
Copy link
Member Author

As you are touching the CurrentValueAsString property and thinking about the best design, let me remind you of this issue #44105 that might affect your current design decision

Thanks for pointing that out. Looking into it, I think we can treat that as a separate enhancement. Whether or not we do that (and I suspect we will), it doesn't stop this PR being valid in its own right. All the test cases being added here, for example, would remain valid. Since #44105 will require some new design, I think we should address that on its own. We will know whether or not any design for #44105 still works with the requirements for this PR based on whether the E2E tests added here still pass. So in summary, I don't think we need to this PR on solving #44015.

@SteveSandersonMS SteveSandersonMS marked this pull request as ready for review February 3, 2023 17:23
@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner February 3, 2023 17:23
Value = value;

_ = ValueChanged.InvokeAsync(Value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note since you mentioned #44105

Do you think it's reasonable to move the logic for invoking ValueChanged as well as EditContext.NotifyFieldChanged to the event handler? (Essentially manually hand-coding @bind:after for the input element binding)

That was my original idea/direction for the issue, but it is breaking-ish. That said, since we are doing a somewhat breaking change here, I think it would be a good idea to tackle both things at the same time (not in this PR) if my suggestion is something we want to follow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a similar suggestion to the one raised at #46434 (comment)

I'm definitely open to doing that but (as per this) would rather keep it separate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially manually hand-coding @Bind:after for the input element binding

Ideally we might even be able to just use @Bind:get/@Bind:set and change the Input* components to be written as .razor files. I started doing that but it derailed the PR and introduced too many independent changes so I think we should tackle it separately.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SteveSandersonMS how would one implement the equivalent of SetUpdatesAttributeName when using .razor files? I just discovered that method today why investigating an issue. It turns out this is what I need but I don't know how to use SetUpdatesAttributeName in a razor file. Is there any way or should I abandon the razor file and use BuildRenderTree?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, <html-element @bind-Value="..." /> in Razor generates the SetUpdatesAttributeName() as part of generated BuildRenderTree() method.

Copy link

@akorchev akorchev May 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @hakenr! I will check if this addresses the issue.

@javiercn
Copy link
Member

javiercn commented Feb 6, 2023

I was previously advocating a risk-averse approach here, but on further consideration I am now siding with the pro-risk option because I don't want to have multiple different kinds of "current value" that component authors would have to understand and distinguish.

I agree, let's do the "right" thing since we are early in the release.

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@SteveSandersonMS SteveSandersonMS merged commit 4e0c598 into main Feb 9, 2023
@SteveSandersonMS SteveSandersonMS deleted the stevesa/continue-40097-input-SetUpdatesAttributeName branch February 9, 2023 14:35
@ghost ghost added this to the 8.0-preview2 milestone Feb 9, 2023
@SteveSandersonMS
Copy link
Member Author

Thanks again, @hakenr! I hope you don't mind, but our branch rules require PRs to be squash-committed, so I had no choice but to do that, which is why your original commits don't show as separate commit entries in the main branch.

@hakenr
Copy link
Member

hakenr commented Feb 9, 2023

Hi @SteveSandersonMS, no problem with that. My intention was to help make Blazor better, not to hunt for position in any contribution leaderboard.

(Consider updating your workflow policies and start using Co-authored-by: in such cases ;-). See GitHub: Commit together with co-authors.)

@ghost
Copy link

ghost commented Feb 9, 2023

Hi @hakenr. It looks like you just commented on a closed PR. The team will most probably miss it. If you'd like to bring something important up to their attention, consider filing a new issue and add enough details to build context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants