Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactions with async functions #152

Open
dArignac opened this issue May 4, 2022 · 4 comments
Open

Reactions with async functions #152

dArignac opened this issue May 4, 2022 · 4 comments

Comments

@dArignac
Copy link

dArignac commented May 4, 2022

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?

@lostpebble
Copy link
Owner

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.

@dArignac
Copy link
Author

dArignac commented May 4, 2022

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.

@lostpebble
Copy link
Owner

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.

@dArignac
Copy link
Author

dArignac commented May 4, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants