question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Asynchronous SetState

See original GitHub issue

Hi 👋

I was playing with immer and it looks like they have support for asynchronous producers.

Then I was looking at a way to have side effects in producers and update the draft accordingly to update zustand’s store but set doesn’t like Promises. So, I was wondering if having set being able to resolve promises and update the state from the resolved value would be something interesting for zustand.

~I was able to make it work but I wonder if there were any drawbacks since I have to explicitely get() the state at the moment of producing the next state. Is it similar to how set receives the current state?~

EDIT: After a few tests I’ve noticed that calling setState multiple times one after the other, doesn’t quite work since they are called synchronously so the draft being passed in to both is of the shape of what get() returns, as soon as the async producer resolves it doesn’t have the previous state update so it overrides it completely. To solve this, we’d have to use the state set gets.

Reproduction: https://codesandbox.io/s/x3rnq036xp — You can see in the console that after the second update, the state has only 2 items instead of 3.

import produce from "immer";
import create from "zustand";

const fetchThing = () => Promise.resolve("thing");

const [, api] = create(set => {
-  const setState = fn => set(produce(fn));
+  const setState = async fn => {
+    set(await produce(get(), fn));
+  };
  return {
    things: [],
    addThing() {
+	  setState(draft => {
+        draft.things.push('first thing');
+      });
	  
+	  // This will override the entire "things" slice whenever it resolves.
      setState(async draft => {
        draft.things.push(await fetchThing());
      });
    }
  };
});

api.subscribe(console.log);
api.getState().addThing();

Thank you! I’m happy to contribute if people are interested in updating the behaviour of set.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
drcmdacommented, May 5, 2019

@Magellol @JeremyRH immer can’t (and won’t) allow async state, but the store can still be transformed towards immer using middleware. The overhead of set remains, but at least it can mutate, and these sets then can then be async. See: https://codesandbox.io/s/5k77o8lzkn

export const immer = config => (set, get) => config(fn => set(produce(fn)), get)

const [useStore] = create(immer(set => ({
  count: 1,
  inc: async () => {
    await delay(2000)
    set(state => void state.count++)
  },
  dec: () => set(state => void state.count--)
})))

The twitter discussion where this came up: https://twitter.com/0xca0a/status/1116390665607421952

2reactions
JeremyRHcommented, May 5, 2019

Hi, thanks for the feedback!

So the problem is the immer draft is stale by the time you mutate it. Even if zustand resolved promises in set, it wouldn’t fix the issue because the resolved value would be stale state.

The only way I can think of solving the issue is to await the async values before calling set:

import produce from "immer";
import create from "zustand";

const fetchThing = () => Promise.resolve("thing");

const [, api] = create((set, get) => {
  const setState = fn => set(produce(get(), fn));
  return {
    things: [],
    async addThing() {
      setState(draft => {
        draft.things.push("first thing");
      });

      const asyncThing = await fetchThing();

      setState(draft => {
        draft.things.push(asyncThing);
      });
    }
  };
});

api.subscribe(console.log);
api.getState().addThing();
Read more comments on GitHub >

github_iconTop Results From Across the Web

Is setState() method async ? - GeeksforGeeks
ReactJs sets its state asynchronously because it can result in an expensive operation. Making it synchronous might leave the browser ...
Read more >
Why is setState in reactjs Async instead of Sync?
This is because setState alters the state and causes rerendering. This can be an expensive operation and making it synchronous might leave the ......
Read more >
setState is an Asynchronous Function - Sentry
The Solution. When the state is actually set can vary. Usually it happens on the next render, but it can sometimes be batched...
Read more >
State and Lifecycle - React
State Updates May Be Asynchronous. React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be ......
Read more >
Why Is setState() Asynchronous In React? | by Harsha Vardhan
In React, we use setState() to update the state of any component. Now setState() does not immediately mutate this state, rather it creates...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found