sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
76.64k stars 3.96k forks source link

Enabling Controlled Components pattern on native components #11159

Open PierreTurnbull opened 3 weeks ago

PierreTurnbull commented 3 weeks ago

Describe the problem

Not sure if this is really a feature request, but I haven't found anything that would lead me to think that it's a bug.

Coming from React, I'm used to the Controlled Component pattern, which is one of the most crucial concepts to build a safe and comprehensible state. It seems to me that this pattern does not apply to native components (div, a, p...) in Svelte, and I cannot find a standard way to implement it. On top of that, the API native components provide us with make it seem that it's the case but it's not.

For example, consider this:

<script>
    let input1Value = "a"
    const input2Value = "b"

    $: console.log(input1Value)
    $: console.log(input2Value)
</script>

<input bind:value={input1Value} />
<input value={input2Value} />

The first input uses the standard Svelte binding. It seems to implement the Controlled Component pattern. Indeed, we can observe that typing in the input results in updating both the input and the state equally.

However, the second example is quite confusing.

The value of input2Value is passed to the second input's value prop, and there is no way the input can update the state. In otherwords, the input's value is locked at "b"... in theory. Actually, when the user types something in the input, the input value is changed. Also, the reactive console.log is not called, which indicates that the state has not changed. To sum it up, the input value changed, while the state did not change, and as a consequence we have 2 sources of truth : the state and the input. This is problematic as it does not respect the Controlled Component pattern and leads to a behaviour that is unexpected from the user perspective. I suppose that the value prop is treated as an initial value for the input, which seems odd to me, but that's just a supposition.

This seems like a bug since it's counter-intuitive: there's nothing that indicates such behaviour, and I expect the input to respect the fact that I control its value, instead of making its own way in a sneaky manner by making the value prop obsolete after the first render.

This also questions how the binding works: when using bind:value everything seems fine, but what's actually going on under the hood? Does the state actually control the input or is it merely updated as a side-effect to the input's value update? I'm concerned about this as it could be an open door to incomprehensible bugs.

Svelte REPL link : https://svelte.dev/repl/74065a77847644e68bf13bb7b7a2ab5a?version=4.2.14

Describe the proposed solution

I would like native components to be controlled whenever they are given a value. That would make the code safer and more comprehensible. That would also make Svelte more accessible, both for new developers and React developers. Ultimately, that would be more inline with Svelte's philosophy of making DX a high priority.

Importance

would make my life easier

MotionlessTrain commented 3 weeks ago

This works analogue to Svelte Components: <input value={input2Value}> sets the value of the input initially, and/or when input2Value changes <MyComponent myprop={value} /> would work in exactly the same way

bind: is telling svelte to update the variable whenever the property changes inside of the component/element. In components, it syncs with the variable used inside of the component, and in case of elements, it uses event handlers to update the variable

The svelte tutorial and the docs have more information about bind:

7nik commented 3 weeks ago

It's because you came from React. When I was testing React after learning Svelte, I found it bizarre and mad that

<input value="test"/>

locks the input to the "test" text.

Vue and Solid have the same behaviour as Svelte: value="test" only sets the initial value.

I think it's done in React to promote their single truth source approach. In fact, React twists quite a lot of native element behaviours compared to vanilla JS.

Edit: also, it will be a breaking change that will piss off a lot of people that get used to the current behaviour.