Open xotahal opened 7 months ago
That's actually the problem. A selector is, by definition, 100% synchronous and pure (same as a reducer, actually). It gets called synchronously, is expected to return a value immediately, and that value is based solely on the arguments that were passed in (typically the Redux store state
, plus optional parameters).
You can write other async function that are similar (in that they take the state as an argument), but they would no longer be selectors.
Where and how would you expect to use one of these "async selectors"?
Appreciate very quick response π
We use https://jsonata.org. It's very simple query language that "unfortunately" works async (in a future version). Here is a simplified example:
const asyncSelector = createAsyncSelector(
(state) => state.a,
(state) => state.b,
async (a, b) => {
const context = { a, b }
return await jsonataEvaluator("a+b", context)
}
Then in React world I would do this:
function PrintAsyncSelector() {
const result = useSelector(asyncSelector)
return <p>{result}</p>
}
function Component() {
return (
<Suspense>
<PrintAsyncSelector />
</Suspense>
)
}
The Component
is suspended while evaluating asyncSelector
. That would mean the useSelector
would need to throw promise.
Another use case would be in "side effects". If you want to get current and derivated data from the store in callback executed on click.
function Button() {
const onSubmit = async () => {
const result = await asyncSelector(store.getState())
await doRestCallWithCurrentValue(result)
}
return <Button onClick={onSubmit}>Submit</Button>
}
I know I could do the await jsonataEvaluator("a+b", context)
directly in the onSubmit
callback, but I'd like to re-use the same code from the selector.
Hope it makes sense.
Yeah, unfortunately that won't work with Redux. useSelector
(and the underlying React useSyncExternalStore
hook) do expect standard 100% synchronous selector functions.
What are you actually trying to retrieve with JSONata? is it actual data in the Redux store state?
Yes, it is data in the Redux store. It is very complex network of createSelector
that collects data from redux and then use jsonata to evaluate the expression.
I guess I could do something like this:
function useHook() {
const [result, setResult] = useState()
const context = useSelector(collectDataFromRedux)
useEffect(() => {
setResult(await jsonataEvaluator("a+b", context))
}, [context])
return result
}
But that complicates the code and it feels like unnecessary work-around of React's Suspense.
you could try using the suspense util library for it?
Part of the issue is it seems like the entire state
is needed as an argument. Normally to get that you'd have to use useSelector(state => state)
, which would force a re-render 100% of the time and is bad.
Sorry, maybe it is just a lack of knowledge about all these libraries. But if I am not wrong there is no problem of making createSelector
work async, right? We can discuss what exactly pure function means and how strict we need to be about it but for the sake of conversation let's say that the following would still be pure action.
We use await
/async
and the jsonata (in our case) evaluates the same inputs to the same outputs without any side effects, I think we can consider such a function as pure.
The problem is the connection between React and Redux, where useSelector
doesn't wait for the reselect's selector to be done. But that shouldn't be a problem to support with a few adjustments, right?
I put together this code sandbox that might better illustrate what I mean - https://codesandbox.io/p/sandbox/use-external-storage-forked-yflsy3
Please let me know if I am missing something
IMO async selectors are the missing key for Redux state management when it comes comparisons of atom-based state libraries. They'd would be a huge upgrade, even if implemented somewhat separately, given the reasons @markerikson mentions here
@kyranjamie : can you give an example of what a notional "async selector" API would look like, how it would be used, and thoughts on it would work?
Say I'm working on an app that has to perform expensive cryptographic operations on an underlying key in a Redux store. Assume a library exposes some logic to derive child key information via an async method.
const rootKey = createSelector(state => state.key);
const childKey = createAsyncSelector(rootKey, async key => await deriveChildKey(key));
function LayoutComponent () {
const childKey = useAsyncSelector(childKey); // suspends or has default value of `undefined`
return <KeyView childKey={key} />;
}
This is super easy in a library like Jotai. I've actually refactored an app to Redux from Jotai and the lack of an API like this is the biggest pain point.
That is effectively the RTK Query useQuery
API:
Hey folks! π First of all, great work on this library! π
I have a question. I'd like to understand where the problem lies. I know that
reselect
doesn't support async selectors, and if we need to fetch data, we should use a thunk or another approach. That's fine and it works. But what if we need to use, let's say, a 3rd party library inside of a selector that computes something, but does that only in an async way?For example, something like this:
Also, if I want to use this as the input of another async selector:
I would just like to understand why this is considered an antipattern. Why can't we support this? This is not a case of fetching data; it's deriving data from the Redux store asynchronously.