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
value
that 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.jsimport { 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?