arnelenero/simpler-state

entity does API call even if persistence is used

Closed this issue · 4 comments

Hi,

why is it that entity() does an API call, although there is a value in localStorage?
here an example:

import { entity, persistence } from 'simpler-state'
import webClient from '../WebClient'

const initializeWebClient = async () => {
    await webClient.requestPrayerTimes()
    return webClient.prayerTimes
}

export const prayerTimesState = entity(initializeWebClient(), [
    persistence('prayerTimesState', {
        deserializeFn: (res) =>
            JSON.parse(res).map((it: any) => {
                it.date = new Date(it.date)
                return it
            }),
    }),
])

In this case I persist the default value in the localStorage. If I reload the page, I expect entity() to use the already persisted value from localStorage. But if I set a breakpoint into the initializeWebClient function, I can see that the function is called.

Weird enough, the deserializeFn is also being called.

Why is that? Thank you in advance for taking time to answer my question.

If you want to test it you can reproduce it in following sandbox https://codesandbox.io/s/shy-glade-ebrq2?file=/src/store/state.ts

the first load only calls initialiteWebClient. If you reload the page the console calls the deserialiser and the initialiteWebClient function

I investigated a little and found out that in persistence.js on line 19 you call origInit(). But this is exactly what I was not expecting from the persistence plugin. Isn't the whole point of this plugin to persist the state somewhere and load it from there the next time?

Btw thank you for this awesome project :)

Thanks for your submission.

On this line:

export const prayerTimesState = entity(initializeWebClient(), [

you were invoking the async function, so at this point simpler-state is not even the one that invoked the function.

That pattern you used is perfectly fine, and it's for async initial value as you intended , BUT because the pattern involves invocation of the async function outside of entity() initializer, it doesn't play well with the persistence pattern. So this is a matter of using two incompatible patterns.

Normally what I would do is intialize the entity as null, keep the persistence as you did it, and then in a useEffect I would call an action, like in your example, the action can be defined like this:

const initializeWebClient = async () => {
    await webClient.requestPrayerTimes()
    prayerTimesState.set(. . .)
}

and then on the appropriate component, usually top level like App, I would have an effect like this:

useEffect(() => initializeWebClient(), [])

Also, it seems your example is missing a matching serializeFn?

Hope this helps.

oh man. I totally forgot that I passed a function to entity. Everything works now after implementing it the way you suggested. The serializeFn is missing, because the default serializer is fine in my case.

Thank you

But wait, we have exactly the same problem if we do it the way you described above. If useEffect is calling initializeWebClient() it will do an API call even though there is a persisted state in the localStorage. It is not a big deal to manually check if there is a persisted state in localStorage, but a plugin which can do the check for you would be nice.

What if we could pass a function to entity(in my case initializeWebClient) and it gets called inside entity and then the returned value will be our initialValue. By doing it this way, entity/plugins could decide if to call the function, or if just use the persisted state.