-
This question is closely related to issues #13101 and #12286. Describe the problemA component receives a string value (representing an ISO 8601 date) as a bindable property. type Props = { date: string }
let { date = $bindable() }: Props = $props(); This can be nicely two-way bound to an input field. If the date value changes in the parent, it will be reflected in the child component; and if the child component changes the value, this will be reflected in the parent component. <input type="text" bind:value={date} /> However, assume that we have a date picker component which expects a let dateAdapter = $state(new Date(date)); <DatePicker bind:value={dateAdapter} /> The problem with this is that when the What I essentially need is a state adapter/proxy which is a two-way binding for states, similar to Describe the (current) solutionAchieving this using The escape hatch is breaking this loop by creating a new state each time the source state is updated. This can be achieved using states within classes (or objects with getter/setters) function adapter<T>(source: T, update: (a: T) => void) {
let state: T = $state(source);
return {
get state() { return state; }
set state(a: T) { state = a; update(a); }
}
} Since we cannot pass states as parameters, we need the The child component then makes use of this as follows: type Props = { date: string }
let { date = $bindable() }: Props = $props();
let dateAdapter = $state(adapter(date, a => date = a.toString()));
$effect(() => { dateAdapter = adapter(date, a => date = a.toString())}) <DatePicker bind:value={dateAdapter.state} />
<!-- dateAdapter always needs to be defined as we are binding to the .state property` --> This works, because when the adapter's value changes, it will update the source. The source update will trigger the effect, which then will reassign the adapter with a new instantiaton. The old adapter will be garbage collected, and the new one will not know anything about the last update. DiscussionThis is an unpleasant solution as the adapter construction needs to be in two different places: once in the A proxy state could further be helpful for unnesting deep state and assigning an alias. For example I hesitated to open an issue for this as I first wanted to get opinions on if this approach can be improved, possibly by using stores or some other techniques. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
The recently-added function bindings for <DatePicker bind:value={() => date_object, (v) => date = v.toString()} /> |
Beta Was this translation helpful? Give feedback.
-
As mentioned, there are now function bindings, but the adapter can also be simplified to just an object that directly wraps the value. No <script>
let { value = $bindable() } = $props();
const converter = {
get value() {
return value?.toISOString().substring(0, 10) ?? '';
},
set value(text) {
value = text == '' ? null : new Date(text)
},
}
</script>
<input bind:value={converter.value} type="date" /> |
Beta Was this translation helpful? Give feedback.
The recently-added function bindings for
bind:
allow you to use$derived
to create theDate
from the string, and write to the string from the binding:playground