S
SolidJS14mo ago
zishh

Constraining input element not working

I want to constraint the values of some input element but I seem to have a misunderstanding of the update cycle of solid. Example:
const [value, setValue] = createSignal("");
return (
<div>
<div>{value()}</div>
<input
type="text"
value={value()}
oninput={e => setValue(e.currentTarget.value.substring(0, 5))}
/>
</div>
)
const [value, setValue] = createSignal("");
return (
<div>
<div>{value()}</div>
<input
type="text"
value={value()}
oninput={e => setValue(e.currentTarget.value.substring(0, 5))}
/>
</div>
)
The value displayed in the div is constrained to 5 characters but in the input itself I can keep writing as long as I want. What I noticed is that this behavior changes depending on whether the cursor in the input is at the end or somewhere else. If it is not at the end it is correctly clipped. Can someone explain this behavior? I would have expected that the input gets its updated value from the signal after the oninput handler ran.
11 Replies
thetarnav
thetarnav14mo ago
your logic doesn't restrict what can be written to the input only what ends up in the signal and because signals do an equality check before triggering updates, assigning it to the same value will not trigger the input to update it's value If you have a string "hello" in the signal and add "!" to it, the trimmed string will still be "hello", so the signal will ignore that change. You can disable that behavior with { equals: false }
thetarnav
thetarnav14mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zishh
zishh14mo ago
Thanks this explains the behavior!
Alex Lohr
Alex Lohr14mo ago
HTML attribute: maxlength - HTML: HyperText Markup Language | MDN
The maxlength attribute defines the maximum number of characters (as UTF-16 code units) the user can enter into an or . This must be an integer value 0 or higher.
Alex Lohr
Alex Lohr14mo ago
If you want to constrain it even more, I wrote our input-mask-primitive for that exact use case: https://github.com/solidjs-community/solid-primitives/tree/main/packages/input-mask
GitHub
solid-primitives/packages/input-mask at main · solidjs-community/so...
A library of high-quality primitives that extend SolidJS reactivity. - solid-primitives/packages/input-mask at main · solidjs-community/solid-primitives
zishh
zishh14mo ago
Thanks! Yes actually the substring was just an example. I want to constraint it to decimal values but the interaction is a bit confusing for me. I guess my issue before was that I tried to constraint the value set to the signal but not the actual input value. Actually I tried to use your library yesterday (edit: nvm it was another one) ! What confused me a bit was that my data model is number | undefined and removing the last character in the input resulted in a value of 0. I have found this behavior in the issue tracker of the project where someone (you?) mentioned to customize the mask implementation. But I could not wrap my head around it - Maybe I should check again. This seems such an easy task but somehow I am approaching it the wrong way.
Alex Lohr
Alex Lohr14mo ago
I really need to improve the documentation of that primitive. What is it you want to achieve?
zishh
zishh14mo ago
Basically just a way to enter a number|undefined and prevent anything else from appearing in the input. Just to demonstrate the idea - Something like this: https://playground.solidjs.com/anonymous/683f3062-099c-46d8-b057-9f6501ba1c02 Requiring the consumer of the component to use { equals: false } doesn't sound very clean. And if the input state is 123 adding a . does not work because it is directly overwritten by the model value. I thought about implementing this as a directive instead - So the constraint handling does not happen on the signal. Does this make sense?
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Alex Lohr
Alex Lohr14mo ago
Is undefined the empty default or do you require your user to input "undefined"? In the former case, you could just use const numMask = createInputMask([/\d+/]) to remove any non-number inputs, then use the following event handler to fill your signal:
const inputHandler = (ev) => {
const value = numMask(ev);
setValue(value = "" ? undefined : parseInt(value, 10));
}
const inputHandler = (ev) => {
const value = numMask(ev);
setValue(value = "" ? undefined : parseInt(value, 10));
}
zishh
zishh14mo ago
Yes the undefined is the empty case. You example works for integer values but not if there is a fractional part. It gets more complicated with the latter because there are intermediate inputs which are not completely valid 123. is fine while inputting text but should become 123 when leaving focus. Better example: https://playground.solidjs.com/anonymous/3c92f707-e386-4778-a2a0-f97ea5e9029d But even with this example the behavior is weird because of the interaction with the signal. If the input value is 123.1 and the user removes the last character, the input becomes 123 instead of 123.. Thanks to the first answer in this thread I understand why this is the case but it is not ideal from a UX perspective.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Alex Lohr
Alex Lohr14mo ago
In that case, you want two masks: one for the input event that allows ending a number with "." ([/\d+(\.\d*|)/]), and another one for the change event that disallows that case (exchange the last asterisk with a plus).