lostpebble / pullstate

Simple state stores using immer and React hooks - re-use parts of your state by pulling it anywhere you like!

Home Page:https://lostpebble.github.io/pullstate

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Reactions with async functions

dArignac opened this issue · comments

Hey there,

first of all: thanks for the lib, I love it!

I'm using the store in a plain class that reacts to store updates and on update I want to execute async code and update the store.
Usually one would do that with a reaction:

this.storeSubscription = MyStore.createReaction(
      (s) => s.someAttribute,
      (watched, draft, original, lastWatched) => { <some code> }
    )

As we cannot have awaits in the reaction function, the async code is in a class method async doAsyncStuff(watched, draft, original, lastWatched). Thus I'd like to bind it:

this.storeSubscription = MyStore.createReaction(
      (s) => s.someAttribute,
      this.doAsyncStuff.bind(this)
    )

However this fails with an immer exception: [Error] Unhandled Promise Rejection: Error: [Immer] produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '[object Promise]'.

Is there a way to use the reactions with async functions?

Unfortunately, currently reactions only support synchronous updates. The current way to do asynchronous updates would be to use a subscription instead, something like this:

async function doAsyncStuff(theme) {
  // ... do things async
  await longTask(theme);
  
  AppStore.update(s => {
    s.theme = // ..whatever updates
  })
}

AppStore.subscribe((s) => s.theme, (watched) => {
  doAsyncStuff(watched);
});

In the future, there may be a way to integrate with async immer producers- but its a bit out of the current scope of the project. If you want to do asynchronous things, there's also Async Actions inside pullstate which deal with these scenarios. Although the docs do need to be updated and simplified, as they have changed over the years to make them more simple to use.

Thanks for the quick answer and let me give you a quick feedback.
With subscription I can handle the async function yes, but I cannot update the store (with the code you outlined). Immer is complaining and throws: Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft. My understanding was that the reactions shall be used for listening to store changes and being able to change store values at once.

Will have a look into the Async Actions later.

Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.

That is a weird message, as it should work as I outlined. I'd have to see your code to know for sure why this is showing up.

You can have a look here: https://github.com/dArignac/kommod/blob/pullstate-152/src/services/storage/StrongholdStorage.ts#L35

The flow is basically that this storage class listens to changes of the token in the SettingsStore. If it changed, it saves the token with the async functionality. The class is instantiated in the App.tsx. If you need more explanation, please just ask.