vue
vue copied to clipboard
Issues with v-model.number
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
, and0
are falsy values. This leads toval !== ''
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!
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?
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.
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.
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
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.
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
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.
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
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 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 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?
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.
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.
v-model.number still doesn't work in firefox 125.0.2 but does in Chrome