vue icon indicating copy to clipboard operation
vue copied to clipboard

Issues with v-model.number

Open printercu opened this issue 6 years ago • 14 comments

Version

2.5.8

Reproduction link

https://jsfiddle.net/50wL7mdz/79189/

Steps to reproduce

Included in fiddle

What is expected?

Input type='number' should not clear values, accept stings formatted in different locales. v-model.number should not return string.

What is actually happening?

Input value is cleared sometimes, v-model.number returns "" for "" and partially typed numbers.


This issue started with topic on forum https://forum.vuejs.org/t/extra-directive-for-number-inputs/22438, it was suggested to open an issue for that. Here is original post:

Hi!

I've found that v-model.number has limitations and requires some boilerplate in some cases. This is mostly because input[type="number"] returns '' for partially input numbers (like 1.). Here are some problems with it:

  • App has no difference when input is either empty or invalid/partial.
  • Bound attribute has to be of [String, Number] type.
  • '', undefined, and 0 are falsy values. This leads to val !== '' checks in all places this attribute is used.

2nd and 3d issues can be solved with computed property, but it's hard to implement for nested ones and array of values.

I came to using separate field for casted values:

<input
  type='number' 
  v-model='obj.val' 
  @input='$set(obj, "valCasted", _nOrNull($event.target.value))'
/>

I wanted to it implement with custom directive (like v-model-number='obj.valCasted'), but I see that v-model is handled differently by compiler. This way it can automatically use $set when property is not defined on object. But I have not found how this can be implemented with custom directives.

So here are questions :) :

  • Is there a better way to work with input[type="number"]? If not:
  • Can this be implemented with custom directives as convenient as v-model is?
  • Should this be added to vue?

After that post I've tried to implement custom component, it's included in fiddle, but it also has some issues. I've also added different types of inputs to fiddle to check their behaviour and checked against different locales.

Thank you!

printercu avatar Nov 27 '17 13:11 printercu

The number becoming 1 is expected as that's what happens in js, they're the same numbers after all. If you need the zeros, you want a string, not a number. but it happens when the user changes focus which is IMO the ux you want here.

About localization, it's handled by the browser and it's out of vue scope. It's the browser that makes some syntaxes work differently on different computers

About your custom component, it behaves how you told it to behave, it modifies the value and adapt it to either null or the number. The part that may be confusing is that you change it to null when it's not valid, doing backspace changes it back to a valid number and triggers a re render, which is why it changes on the input as well

cc @LinusBorg What was the part that you thought we might improve?

posva avatar Nov 27 '17 15:11 posva

Thanks for feedback!

I've included plain input[type=number] to fiddle to check it's behaviour in this cases. You can see that it does not clear trailing zeroes at all. Actually I also don't see a problem in stripping trailing zeroes on blur, but I think it should not clear them when changing 1.001 to 1.002. It's the 1) and 4) cases in fiddle.

Original post was more about how to make v-model.number to cast values to Number|null but not Number|String as it is now. I've made some proposal but it's not clear for me how to implement it. As I understood, Linus said that it may be good if vue provide this feature itself. Please check 'Here is original post: Hi! I've found that... ' in first comment for more info.

printercu avatar Nov 27 '17 15:11 printercu

For now I don't see how it can be done without having 2 separate fields: 1 - string to keep input[type=number] fine, and 2 - numeric for app. Is it possible somehow to prevent rerender of input on input event? I think this could solve all the issues.

printercu avatar Nov 27 '17 15:11 printercu

but I think it should not clear them when changing 1.001 to 1.002.

That only happens with the custom input. The explanation is here:

The part that may be confusing is that you change it to null when it's not valid, doing backspace changes it back to a valid number and triggers a re render, which is why it changes on the input as well

v-model.number will cast values to Number or empty string, but you're of course free to create your own custom component that handles things differently using or not the number modifier

posva avatar Nov 27 '17 16:11 posva

That only happens with the custom input

Not only, see 1) case in fiddle (fill 1st input with 1.0000.0 ...), though it's for malformed value.

but you're of course free to create

So is it not interesting to vue team to provide more type-strict interface? It's error prone and such errors are easy to leave, because there are no exceptions on misuse. Also to check that input is empty it's not enough to val != null but it also requires val != null && val !== '' in every place it's used.

printercu avatar Nov 27 '17 17:11 printercu

It does not happen with 1), or I'm not getting the instructions(typing 1.0000.0, then backspace x2).

it also requires val != null && val !== '' in every place it's used.

No, it does not. It's either a number or '' no null. In your example, it's null because that's the initial value you're giving to it Vue's job here is to link the value of the input to the state and make it a number whenever possible because of the number modifier. The empty string is actually very useful because it allows you know if the input contains a valid number, it makes it flexible to use in different scenarios

posva avatar Nov 27 '17 17:11 posva

No, it does not.

Here is example: https://jsfiddle.net/hbov4mmr/8/ Checks item.value != null && item.value !== '' are repeated 3 times.

The empty string is actually very useful

I'm not sure it's useful at all, because there is same value for empty string and for invalid value. In locales with , delimiter 1. is invalid (returns '') but 1.2 is valid and returns 1.2. So empty string has ambiguous, very different meanings.

printercu avatar Nov 27 '17 18:11 printercu

Your example is completely different: the value is not initialized, it's as if you explicitly set it to undefined. Checking if an input is dirty or not is yet, another different thing, unrelated to numbers

I'm not sure it's useful at all, because there is same value for empty string and for invalid value. In locales with , delimiter 1. is invalid (returns '') but 1.2 is valid and returns 1.2. So empty string has ambiguous, very different meanings.

That's the browser setting it to an empty string... Number validation is out of Vue scope

Anyway, just waiting for @LinusBorg input on what could be improved

posva avatar Nov 27 '17 20:11 posva

Once again, this all is not about checking if an input is dirty, but about providing more solid and type-strict interface: if it returns numbers it should not return empty string or any other string. Returning empty string now is just a workaround, and i think it can be fixed.

Can we please treat it more as feature request and ask few more maintainers look at it.

I also wonder why I can not implement v-model.number in "user-space": https://jsfiddle.net/rubx2fcb/1/ Here I took all the code from internals (https://github.com/vuejs/vue/blob/dev/src/platforms/web/compiler/directives/model.js) but it still does not allow to input 1.0 into this input. Am I missing something?

UPD. I was checking in chrome. In safari It allows to input 1.0 or 1,0 but clears trailing zeroes when removing last digit in 1.0001. FF does not respect OS locale settings and ignores 1,23 formatted numbers, while still has issue with removing last digit in 1.0001.

printercu avatar Nov 28 '17 07:11 printercu

@printercu Here's a try at making a numeric input component: https://codesandbox.io/embed/2wrqj87q0y?module=%2FApp.vue . It uses NaN as the empty / not valid value. The only issue it has is that if you typed an invalid number, you can't clear it by setting the v-model value to NaN, as it can't distinguish between empty and not valid.

lbogdan avatar Dec 04 '17 22:12 lbogdan

@lbogdan Looks great! Thank you! I've slightly changed and it also works with null instead of NaN: https://codesandbox.io/embed/4wk0k64yw0. I will give it a try in the app soon.

Can you please help with (from my previous comment):

I also wonder why I can not implement v-model.number in "user-space": https://jsfiddle.net/rubx2fcb/1/ Here I took all the code from internals (https://github.com/vuejs/vue/blob/dev/src/platforms/web/compiler/directives/model.js) but it still does not allow to input 1.0 into this input. Am I missing something?

printercu avatar Dec 05 '17 07:12 printercu

With v-model.number happily returning "" instead of null, for empty values, I'm not sure what the value of v-model.number even is. Isn't the entire point of the directive to ensure that the value is actually a number? If not, I would argue it's named inappropriately.

rmirabelle avatar Apr 27 '21 20:04 rmirabelle

At the moment v-model.number doesn't even produce a number if there is text in the input. It only works if the first character is a number (only tested this in Firefox).

It's easy to reproduce, just go to https://vuejs.org/guide/essentials/forms.html#basic-usage and click "Try it in the Playground" and add .number to v-model. Then just type text in the "edit me" field.

Nicate avatar Jun 23 '22 13:06 Nicate