0

There are parts of my app that must work synchronously. I am using zustand. The problem is that zustand's setState function works asynchronously. Please let me know if there are any other libraries that support synchronous state changes or any tricks.

App.js (react example)

// in real
import create from 'zustand'
import logo from './logo.svg';
import React, { useEffect } from 'react';

const useStore = create(() => ({
  counter: 0
}))

function App() {
  const { counter } = useStore()
  useEffect(() => {
    console.log(counter) // output: 0
    useStore.setState({ counter: counter + 1 })
    console.log(counter) // output: 0
    useStore.setState({ counter: counter + 1 })
    console.log(counter) // output: 0
  }, []);

  return (
   <div></div>
  );
}

export default App;

// my hope

// ...
useEffect(() => {
    console.log(counter) // output: 0
    useStore.setState({ counter: counter + 1 })
    console.log(counter) // output: 1
    useStore.setState({ counter: counter + 1 })
    console.log(counter) // output: 2
  }, []);


// ...
  • 1
    *"There are parts of my app that must work synchronously."* Why? Please elaborate. Btw, zustand's setter [is synchronous](https://github.com/pmndrs/zustand/blob/12c81383ac81de67b84ba7b63ffc2e81a8e66e76/src/vanilla.ts#L83-L97). The effect you see is a result of how React works (`counter` will always refer to the value of the current "render iteration"; updating `counter` will cause a re-render of the component, making the updated value available in the next "render iteration". – Felix Kling May 18 '22 at 08:58
  • @FelixKling is absolutely right in his explanation, to make that React "behaviour" synchronous you should wrap the setState in a useCallback, that should work, not very familiar with zustand's tho – Jorge Guerreiro May 18 '22 at 09:13
  • @JorgeGuerreiro wrap the setState? I did like this but it doesn't work. Maybe I misunderstood, could you show me the example code? `const test = useCallback( (args) => { useStore.setState(args) }, [], )` – user17692924 May 18 '22 at 11:41

1 Answers1

0

The problem is in your counter definition.

const { counter } = useStore()

should be

 const counter = useStore((state)=> state.counter)

Here's an example of how your code should look:

store:

const useStore = create(() => ({
  counter: 0,
  addOne: () =>
        set((state) => ({
            counter: state.counter +1
        })),
 }))

App:

function App() {
  const counter = useStore((state)=> state.counter)
  const addOne = useStore((state)=> state.addOne)

  useEffect(() => {
   let interval = setInterval(() => addOne(), 1000)
   return () => clearInterval(interval)
  }, [counter]);

  return (
   <div>{counter}</div>
  );
}

I'm throwing the setInterval function in there so that it updates once every second. Using the counter state as a dependency for useEffect will cause the component to re-render every time the state is changed.

Z Monk
  • 37
  • 7
  • I was wondering if you might know of a way to handle this without the use of useEffect. – GiselleMtnezS Oct 13 '22 at 14:15
  • We have been using zustand to handle state, and we are using it like in your example at the moment. However, it is resulting in a very large net of useEffects everywhere and we are working in changing this to reduce the amount of re-renders needed. I have found a way to bypass the react behavior with async/await in some cases but we have heard it is not recommended. – GiselleMtnezS Oct 13 '22 at 14:18
  • Oh, is this something I should create a new question for? – GiselleMtnezS Oct 13 '22 at 14:18