Search code examples
storesvelte

Svelte: Store Data Not Being Reactive When Component Changes Data and Vice Versa


I'm sure this is a super easy fix, but I'm having an issue where I setup a writable store, and it's mostly reactive, except when a component changes the data, the reactivity in the App file doesn't fire and vice versa. Here's the code:

App.svelte:

<script>
    import { data } from './store.js'
    import Component from './Component.svelte'
    let localData
    data.subscribe((value) => {
        localData = value;
    });
</script>

<h2>In App.svelte</h2>

<p>Hello {localData.name}!</p>

<input name="name" type="text" bind:value={localData.name}>

<p>
    {localData.details.number}
</p>

<h2>In Component.svelte</h2>

<Component />

Component.svelte:

<script>
    import { data } from './store.js'
    let localData
    data.subscribe((value) => {
        localData = value;
    });
</script>

<input type="number" bind:value={localData.details.number}>

<p>Hello {localData.name}!</p>
<p>{localData.details.number}</p>

store.js:

import { writable } from 'svelte/store'

export let data = writable({
    name: 'Bob Smith',
    details: {
            dob: '1982/03/12',
            favoriteFoods: ['apples', 'pears', 'bourbon'],
            number: 1
        },
})

And, if you want to use it in the Svelte REP: https://svelte.dev/repl/164227336d6c4cc29f7ea0a15e89c584?version=3.44.3


Solution

  • You are subscribing to the data and putting it into a local variable and then bind to that. This means the store does not know that anything changed and updates won't be propagated. Two options:

    First option: You get rid of the two way binding and update the store explicitely like this:

    <script>
        import { data } from './store.js'
        import Component from './Component.svelte'
        let localData
        data.subscribe((value) => {
            localData = value;
        });
    
        function updateName(evt) {
            const newName = evt.target.value;
            data.update(value => ({...value, name: newName }));
        }
    </script>
    
    <h2>In App.svelte</h2>
    
    <p>Hello {localData.name}!</p>
    
    <input name="name" type="text" value={localData.name} on:input={updateName}>
    
    <p>
        {localData.details.number}
    </p>
    
    <h2>In Component.svelte</h2>
    
    <Component />
    
    

    This is very explicit but also a bit boilerplate-y. We have Svelte's handy auto subscription feature, so let's use that instead. Second and prefered option:

    <script>
        import { data } from './store.js'
        import Component from './Component.svelte'
    </script>
    
    <h2>In App.svelte</h2>
    
    <p>Hello {$data.name}!</p>
    
    <input name="name" type="text" bind:value={$data.name}>
    
    <p>
        {$data.details.number}
    </p>
    
    <h2>In Component.svelte</h2>
    
    <Component />
    

    Notice how we got rid of all the subscription boilerplate. $data accesses the current state of the store and since it's a writable store you can also write back to it that way. You can read more about stores in the docs: https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values