square / svelte-store

528 stars 36 forks source link

`persisted()` returns `undefined` first time it's accessed, even when `initial` is set #81

Open FluffyDiscord opened 1 year ago

FluffyDiscord commented 1 year ago

I have the following setup

export type LastLocation = {
    location: null|[number, number]
    state: "DENIED"|"ALLOWED"|"UNKNOWN"|"ASKED"
}

export const lastLocation = persisted<LastLocation>({
    location: null,
    state: "UNKNOWN",
}, "last-location", {
    storageType: "LOCAL_STORAGE",
})

which I then use in my component

<script lang="ts">
    import {lastLocation} from "./utils"

    if($lastLocation.state === "UNKNOWN") {
        // error
    }
</script>

The problem is, that the first time the store is initialized and saved to the local storage with it's default value, $lastLocation is undefined. I need to reload the store/page. image

This line is the culprit https://github.com/square/svelte-store/blob/main/src/persisted/index.ts#L140

The initial value should be passed, NOT undefined

rafadess commented 11 months ago

I have the same issue with persistent, it starts with undefined even though it has initial value.

Maybe, one way to fix it is passing the initial value to the thisStore writable. This way subscribe function will return the correct initial value.

let initialValue

if (isLoadable(initial)) {
 initialValue = await initial.load();
} else {
 initialValue = initial
}

const thisStore = writable<T>(initialValue, (set) => {
  initialSync = synchronize(set);
});

https://github.com/square/svelte-store/blob/44341f5166826982d947c9dd8bc63193911c8a74/src/persisted/index.ts#L140C3-L140C3

The if conditional could go into a dedicate function, since it is the same code inside of syncronize. Any way, this is just a possible solution. 😉

based64-eth commented 9 months ago

I have the same issue with persistent, it starts with undefined even though it has initial value.

Maybe, one way to fix it is passing the initial value to the thisStore writable. This way subscribe function will return the correct initial value.

let initialValue

if (isLoadable(initial)) {
 initialValue = await initial.load();
} else {
 initialValue = initial
}

const thisStore = writable<T>(initialValue, (set) => {
  initialSync = synchronize(set);
});

https://github.com/square/svelte-store/blob/44341f5166826982d947c9dd8bc63193911c8a74/src/persisted/index.ts#L140C3-L140C3

The if conditional could go into a dedicate function, since it is the same code inside of syncronize. Any way, this is just a possible solution. 😉

This is not an acceptable solution in the case that the initial value NEEDS to be equal to the value that the user has stored in localStorage.

thanhnguyen2187 commented 8 months ago

Hi! I'm having the same problem. Is there any way to fix it?

nikicat commented 7 months ago

I have the same issue with persistent, it starts with undefined even though it has initial value. Maybe, one way to fix it is passing the initial value to the thisStore writable. This way subscribe function will return the correct initial value.

let initialValue

if (isLoadable(initial)) {
 initialValue = await initial.load();
} else {
 initialValue = initial
}

const thisStore = writable<T>(initialValue, (set) => {
  initialSync = synchronize(set);
});

https://github.com/square/svelte-store/blob/44341f5166826982d947c9dd8bc63193911c8a74/src/persisted/index.ts#L140C3-L140C3 The if conditional could go into a dedicate function, since it is the same code inside of syncronize. Any way, this is just a possible solution. 😉

This is not an acceptable solution in the case that the initial value NEEDS to be equal to the value that the user has stored in localStorage.

But it's better than the current state.

rafadess commented 7 months ago

I have the same issue with persistent, it starts with undefined even though it has initial value. Maybe, one way to fix it is passing the initial value to the thisStore writable. This way subscribe function will return the correct initial value.

let initialValue

if (isLoadable(initial)) {
 initialValue = await initial.load();
} else {
 initialValue = initial
}

const thisStore = writable<T>(initialValue, (set) => {
  initialSync = synchronize(set);
});

https://github.com/square/svelte-store/blob/44341f5166826982d947c9dd8bc63193911c8a74/src/persisted/index.ts#L140C3-L140C3 The if conditional could go into a dedicate function, since it is the same code inside of syncronize. Any way, this is just a possible solution. 😉

This is not an acceptable solution in the case that the initial value NEEDS to be equal to the value that the user has stored in localStorage.

Hunn, how about this then:

let initialValue
const storageKey = await getKey();
const stored = getStorageItem(storageKey);

if (stored) {
  initialValue = stored
} else {
  if (isLoadable(initial)) {
   initialValue = await initial.load();
  } else {
   initialValue = initial
  }
}

const thisStore = writable<T>(initialValue, (set) => {
  initialSync = synchronize(set);
});

I can make a PR with this, if the maintainers agree with.

nikicat commented 7 months ago

I've solved my case with such a wrapper

function fixPersisted<T>(initial: T, store: Writable<T>): Writable<T> {
  return {
    subscribe: (run: (value: any) => void, invalidate?) => {
      return store.subscribe((value: any) => {
    if (value !== undefined) {
      run(value)
    } else {
      run(initial)
    }
      }, invalidate)
    },
    set: store.set,
    update: store.update,
  }
}

It doesn't wait to load the persisted value and always returns the initial value on start.