Git Product home page Git Product logo

Comments (10)

dy avatar dy commented on May 3, 2024

That does not even allow to make sync selectors.

useStore(state => state.query(), [])

// in query
const [useStore] = create((set, get) => ({
query: () => (state.set({loading: true}), state.set({loading: false}))
}))

this raises unstoppable infinite recursion, so that even empty deps [] don't guard.

Expectation:

  1. Gate deps [] should run selectorFn just once for the deps set, even if previous selector is not finished.
  2. Calling (indirectly) set from selectorFn should not trigger component rerender instantly to cause recursion.

from zustand.

drcmda avatar drcmda commented on May 3, 2024

Not sure if calling into state inside the selector is a good idea. The selector is fired on every state change. Now the selector calling set again is pretty wild as well, shouldn’t you just fetch query and call it inside useEffect?

from zustand.

dy avatar dy commented on May 3, 2024

Well that’s just a footgun, I shouldn’t I guess, but that would be useful combination of useEffect, useState, store and hookleton/global state in one. Now that is not as much useful for async methods, like fetching - they oftentimes come with store.

from zustand.

drcmda avatar drcmda commented on May 3, 2024

i mean, not opposed, but do you have an idea how we could solve this?

from zustand.

dy avatar dy commented on May 3, 2024

I used to set “busy” flag to a function via https://ghub.io/icicle, but more generally that is sort of “mutex”.
Recently I’ve postponed setting to ‘useEffect’, so querying happened in different hook.
But I think some code rearrangement could help, should have a look at the source more thoroughly.

from zustand.

JeremyRH avatar JeremyRH commented on May 3, 2024

The selector isn’t meant to be used for effects. Supporting this would be very difficult or impossible with the current design. The selector is called when the state is updated to determine if the selected state has changed. This leads to an infinite loop if you set state in the selector: setState()-> selector() -> setState()-> selector() etc.

from zustand.

drcmda avatar drcmda commented on May 3, 2024

@JeremyRH i think in redux there was something that made it possible. Could setState set a flag and if it's set it will postpone the update?

let active = false
let queue = []
let raf
function setState(...args) {
  if (active) 
    // push arguments to queue
    queue.push(...args)
    // cancel old frame and create new
    if (raf) cancelRequestFrame(raf)
    raf = requestAnimationFrame(setState)
  }
  active = true
  // merge queued up state and empty queue
  queue.forEach(...set state)
  queue = []
  // merge current state (if any)
  ... set args state
  ... call listeners
  active = false
}

from zustand.

JeremyRH avatar JeremyRH commented on May 3, 2024

@drcmda You would end up with inconsistencies in state because setState is no longer guaranteed to set state synchronously:

set({ count: get().count + 1 }) // get() wont return queued state

You can solve this by always setting state even if active is true. The problem is the listeners are called.

Also asynchronous effects would not be blocked by this:

const useStore = create(set => ({
  something: {},
  async selectorWithEffect() {
    const something = await waitForSomething()
    set({ something })
    return something
  }
}))
function App() {
  const promise = useStore(s => s.selectorWithEffect())
}

The callstack would be something like:

etc.
...async
(active = false)
waitForSomething
selectorWithEffect
set (active = true)
...async
(active = false)
waitForSomething
selectorWithEffect
set (active = true)
...async
waitForSomething
selectorWithEffect
useStore

from zustand.

JeremyRH avatar JeremyRH commented on May 3, 2024

My thoughts:

I don't think effects inside the selector is a good pattern to follow. useStore is more like useState or useReducer, not useEffect. We have to call the selector when state changes to get a new slice. Stopping set from calling the listeners is patchwork. There could be other effects outside of set that we have no control over. It seems like supporting this would lead to more problems than it solves. If we had the ability to only run the selector once, or when the dependencies list changes, it might be worth implementing. But I don't think this is a good idea right now.

from zustand.

drcmda avatar drcmda commented on May 3, 2024

Makes sense. It was just something i thought remembered about redux. But they probably came to the same conclusion: https://stackoverflow.com/questions/36730793/can-i-dispatch-an-action-in-reducer

from zustand.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.