Search code examples
sveltesveltekitimmer.jssvelte-store

custom svelte store with immer is refreshing while data is same, also data binding is not working


The issue with this code is that the store triggering its subscribers but the data has not changed. The button is simply setting sub.a=10. However the store is refreshing which is not supposed to. Immerjs is supposed to help with this. Also the data binding is not working and this is related to the set and probably update methods on the custom store. How could I achieve these functionalities?

This is the custom store:


import { writable } from "svelte/store";
import produce from "immer";

const immerWritable = (value) => {
  const store = writable(value);

  function set(new_value) {
    if (new_value !== value) {
      store.set((value = new_value));
    }
  }

  const producer = (fuc) => store.update((state) => produce(state, fuc));

  return {
    producer,
    set,
    update: (fn) => set(produce(value, fn)),
    subscribe: store.subscribe
  };
};

const store = immerWritable({
  sub: { a: 1, b: 2 },
  sub2: { a: 1, b: 2 }
});

const actions = {
  setSubA_10: () =>
    store.producer((draft) => {
      draft.sub.a = 10;
    })
};

export default { ...store, ...actions };

the app.svelte

<svelte:options immutable={true} />
<script>
  import Changed from "./Changed.svelte";
  import store from "./store";
  import { onDestroy } from "svelte";
  let changed = 0;
  const sub = store.subscribe(s => {
    changed++;
  });
  onDestroy(sub);
</script>




<div>store changed {changed} </div>
<button on:click={store.setSubA_10} >set sub.a to 10</button>
<input bind:value={$store.sub.a} type="number"/>

https://codesandbox.io/s/svelte-playground-forked-t21xfi?file=/App.svelte:0-412


Solution

  • I found valtio could help in creating a store that does not trigger a refresh if data is the same.

    Here is the store file:

    import { snapshot, subscribe, proxy, } from "valtio"
    import { derive } from 'valtio/utils'
    
    
    
    export const proxyToReadable = (proxyState: any) => ({
      subscribe(fn: (value: any) => void) {
        fn(snapshot(proxyState))
        return subscribe(proxyState, () => fn(snapshot(proxyState)))
      }
    })
    
    
    const proxyStore = proxy({
      sub1: { a: 1, b: 2 },
      sub2: { c: 3, d: 4 },
    })
    
    //you can add derived reactive properties
    derive({
      onlyC: (get): number => get(proxyStore).sub2.c,
    },
      {
        proxy: proxyStore,
      })
    
    
    //to be attached as an action. it only changes the value once. any further calls will never trigger a refresh.
    const inc = () => proxyStore.sub2.c = 1
    
    //the final assembly
    //note that you need to export the proxy as well if intent to edit/bind the data without relying on pre-defined action functions like `inc`.
    export default { ...proxyToReadable(proxyStore), inc, proxy: proxyStore }
    
    <script>
      import store,{proxyToReadable} from "./store";
    
      //you can subscribe to a sub data, if sub2 change, this sub1 will not trigger
      const sub1 = proxyToReadable(store.proxy.sub1)
      $: console.log("store",$store)
      $:console.log("store",$sub1)
    </script>
    
    <button on:click={() => store.inc()}>inc</button>
    <input type="text" bind:value={store.proxy.sub1.b} />