Two-way binding in Svelte
Probably every time I’ve implemented anything slightly complex in Svelte I’ve wondered if React’s one-way binding is the way to go after all. I don’t think it is, but you do have to do things a certain way in Svelte to avoid getting into trouble with two-way binding.
Using a component that provides a numeric form input and converts the value to a number as an example, you can quickly get yourself tangled up in an infinite loop or other weird bugs if you try and get the following behaviour:
The native input value is a stringified version of the number in the component’s
value
prop.The component’s
value
prop, via two-way binding and reactive assignment, is the input value converted to a number.
The way to get this working is to only propagate the updates in one direction at a time: while the user is focusing the input, update the numeric value based on the input value; and while not focused, update the input value based on the numeric value.
<script>
export let value;
let inputValue;
let focused = false;
$: if (!focused) updateInputValue(value);
$: if (focused) updateValue(inputValue);
function parse(s) {
s = s.replace(/D/g, "");
if (!s.match(/^d+$/)) {
return null;
}
return Number(s);
}
function updateValue(inputValue) {
value = parse(inputValue);
}
function updateInputValue(value) {
inputValue = value === null ? "" : String(value);
}
function onFocus() {
focused = true;
}
function onBlur() {
focused = false;
updateInputValue();
}
</script>
<input
bind:value={inputValue}
on:blur={onBlur}
on:focus={onFocus}
/>
This way there is no circular dependency, and you don’t mess with the input while the user is typing in it.
Note: I haven’t tried this with Linux middle-click pasting, but a quick test shows that this does cause a focus
event to fire before paste
, so I would bet that it works.