-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Infinite recursion in selector #10
Comments
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 Expectation:
|
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? |
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. |
i mean, not opposed, but do you have an idea how we could solve this? |
I used to set “busy” flag to a function via https://ghub.io/icicle, but more generally that is sort of “mutex”. |
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: |
@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
} |
@drcmda You would end up with inconsistencies in state because set({ count: get().count + 1 }) // get() wont return queued state You can solve this by always setting state even if 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:
|
My thoughts: I don't think effects inside the selector is a good pattern to follow. |
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 |
Now these awesome storage hooks as side-effect solve the task of singleton-hook, like hookleton, so that makes them useful for eg. history/routing.
Would be super-cool also to incorporate "use-effect"-purpose into the standard selector function as
Now that selector creates nasty recusion, making that method of querying data useless.
What would be the right solution here? That is not elegant to put fetching logic in a separate
useEffect(fetchingLogic, [...query])
.The text was updated successfully, but these errors were encountered: