Two way binding is great and elegant in Svelte, but a recurrent situation I've come across is needing two way binding with an intermediate transformation that converts types or does some kind of clean up. For example:
- Binding to a select component's prop
valuethat has the form{value, label}, but its parent just handles a value - Type conversions, where a
<input type=text>is also input for some other type (Number, date, custom one), or an input to edit an object as JSON that could also be changed from the outside.
My question is: Which is a good, idiomatic and simple way of solving this pattern in Svelte?
The best reusable solution I've found so far has been to create a store factory for one-to-one transformations that returns two stores, a and b which you can then use and bind to other components:
// App.svelte
<script>
import oneToOne from './oneToOne.js'
const f = x => JSON.stringify({x});
const fInv = x => {try{return JSON.parse(x).x} catch(err){return NaN}};
let [a, b] = oneToOne(13, f, fInv);
</script>
A: <input bind:value={$a}/>
B: <input bind:value={$b}/>
// oneToOne.js
import { writable } from 'svelte/store';
const identity = x => x;
export default function oneToOne(val, f = identity, fInv = identity) {
let fInvB = val;
let fA = f(val);
const A = writable(fInvB);
const B = writable(fA);
B.subscribe((b) => {
if(fA !== b && !(Number.isNaN(fA) && Number.isNaN(b))) {
fInvB = fInv(b);
fA = b;
A.set(fInvB)
}
});
A.subscribe((a) => {
if(fInvB !== a && !(Number.isNaN(fInvB) && Number.isNaN(a))) {
fA = f(a);
fInvB = a;
B.set(fA)
}
});
return [A, B];
}
Does this make sense? Am I missing a simpler way of doing this or avoiding this complexity altogether?