I have a Svelte component that allows editing a list of strings, with the list coming from the writable store $places. This is displayed in the web UI as a list of tags, one longer than the list of places (so that people can add to the list).
let inputs: string[] = []
$: {
inputs = [...$places, '']
}
The web code renders as follows:
<label for="place0">Places</label>
{#each inputs as input, idx}
<input id="place{idx}" name="places" type="text" bind:value={input} />
{/each}
For some reason I can't understand, any attempt to type in these input fields causes Svelte to assume that both inputs
and $places
have been invalidated. Because it thinks $places
has been invalidated, the first code block above gets triggered, essentially overriding whatever the user was trying to type and making the field effectively read-only.
I have verified this behavior looking at the compiled code:
function input_input_handler(each_value, idx) {
each_value[idx] = this.value;
$$invalidate(0, inputs), $$invalidate(1, $places);
}
As you can see, the last line marks both inputs
and $places
as invalidated. I can't see any reason why $places
should be invalidated, and it's breaking my component.
Can anyone explain why this is happening and how to prevent or circumvent it? Many thanks....
...how to prevent or circumvent it
If $places
is already initialized when the component is mounted, no reactive statement would be needed and just let inputs = [...$places, '']
would work
If $places
might not yet be set, a subscription could be used instead of the $:
reactive statement REPL
<script>
import {places} from './places'
let inputs = []
const unsubPlaces = places.subscribe($places => inputs = [...$places, '']) // unsub in onDestroy()
</script>
{#each inputs as input, idx}
<input id="place{idx}" name="places" type="text" bind:value={input} />
{/each}
or
<script>
import {places} from './places'
let inputs = []
$: $places, initInputs()
function initInputs() {
inputs = [...$places, '']
}
</script>
{#each inputs as input, idx}
<input id="place{idx}" name="places" type="text" bind:value={input} />
{/each}
Like this all values are stored inside inputs
, based on $places
but with no 'backward connection'.
With H.B.'s answer the input values are split and the $places
ones are still connected to the store plus the seperate new one stored in inputs
. Depends on how you need and want to handle the data.