Creating single state object from multiple stores data with Svelte derived store

1.8k Views Asked by At

I tried implementing a custom Svelte store, which would allow me to "throw" one single state object at the subscribing page. Here's an example code I prepared:

import { derived, writable } from 'svelte/store'
import SomeOtherStore from '$stores/otherstore.js'
import { page } from '$app/stores'

const initialState = {
    loading: true,
    items: [],
    param: null,
}

const createPageStore = () => {
    // Create main store since derived store cannot be updated, set initial state
    const mainStore = writable(initialState)
    const { update } = mainStore

    // instantiate other store needed to construct page state
    const someOtherStore = SomeOtherStore()

    function someKindOfFunctionModifyingState() {
        // Need to update the state of items derived from SomeOtherStore, but the items do not exist here as the state was not propagated to the main store
        update(state => {
            return {
                ...state,
                items: state.items.map(stringItem => stringItem.capitalizeFirst())
            }
        })
    }

    // combine stores in order to construct page state
    const { subscribe } = derived([mainStore, page, someOtherStore], ([$mainStore, $page, $someOtherStore]) => {
        const { someItems, loading } = $someOtherStore
        
        return {
            ...$mainStore,
            loading: $mainStore.loading || loading,
            param: $page.url.searchParams.get('param'),
            items: someItems,
        }
    })

    return {
        someKindOfFunctionModifyingState,
        // return subscribe method from derived store
        subscribe,
    }
}

let instance

export default function PageStore() {
    return instance ?? (instance = createPageStore())
}

mainStore is how I thought I would be able to do this, since derived stores cannot be updated

As you can see, I need to construct the example items in my state object using data provided by SomeOtherStore.

The problem I have is that when I need to update the state of those items, I cannot do it because items array is empty in mainStore and I don't know how to pass the resulting derived state to the mainStore.

Obviously to no surprise if I try to set the final derived state to mainStore from within the derived store function, it results in a loop.

<script>
  import PageStore from '$stores/pageStore.js'

  const store = PageStore()
  
</script>


{#each $store.items as item}
  <span>{item}</span>
{/each}
<button on:click={store.someKindOfFunctionModifyingState}>
  Click to capitalize first letter
</button>

example usage in page component

TL;DR Basically my need is to be able to create single object state combined from multiple stores and be able to update that state when for example click events happen.

How can I achieve this?

1

There are 1 best solutions below

2
On

Instead of

 const { subscribe } = derived(...

write

 const combined = derived(...

That would allow the someKindOfFunctionModifyingState function to use the get utility.

import { get } from "svelte/store";

function someKindOfFunctionModifyingState() {
  const $combined = get(combined);
  mainStore.update(($mainStore) => ({ 
    ...$mainStore,
   items: $combined.items.map(stringItem => stringItem.capitalizeFirst())
  }))
}

Using get has a small performance overhead, but that's negligible for click events