-
Notifications
You must be signed in to change notification settings - Fork 672
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
Handling asynchronous select #142
Comments
Reselect is not intended for this use case, it is for computed derived data from data that is already in the store. Try looking at Redux Thunk or, if more complicated orchestration is required, Redux Saga or Redux Loop. |
Thanks for coming back to me, apologies for replying late. Unless I'm wrong, none of redux-thunk, redux-saga or redux-loop fit my requirements as they all end-up updating the state with the asynchronous data. In my case, storing the itineraries in the store doesn't make sense, as itineraries result from the actual data in my store. The only thing I'd like to control is when they shall be recalculated. After some deep thought about it, my conclusion is that the logic needs to lie in the component itself: ...which felt like this is what reselect does, at least synchronously ! So regarding my case, wouldn't it make sense to handle asynchronous selects? How would you deal with it otherwise? Cheers |
I had a similar use case, and I ended up having to copy the main function memoizeLastPromise(func) {
let lastArgs = null;
let lastResult = null;
return (...args) => {
if (
lastArgs !== null &&
lastArgs.length === args.length &&
args.every((value, index) => value === lastArgs[index])
) {
return lastResult;
}
return func(...args).then((ret) => {
lastArgs = args;
lastResult = new Promise((res) => res(ret));
return ret;
});
};
} I think it makes sense for this to be within the library itself. Rather than being tied to |
If you do async calls in the selector it is not going to play nicely with Redux Devtools, which achieves time travel by replaying a series of actions. As Reselect is primarily a companion library for Redux I don't think it is a good idea to introduce anything that breaks Devtools or encourages non-idiomatic patterns. That said, |
+1 Agreed that this would be a very useful addition, so it would be nice to see this reconsidered. |
Selectors have never been meant for asynchronous behavior - they're intended for synchronously reading values for a state tree, with memoization of derived results. |
Found this project https://github.com/humflelump/async-selector that solves this issue. |
Hey, I made a small hook https://www.npmjs.com/package/use-async-selector that could help. |
I noticed that there is no 'createSelectorCreator' reexport in rtk for 'reselect'. But what if I want to do some lazy loading? Such as when I select 'point1' from store, if there is no valid value, it should fetch for 'point1', update store and give updated value to selectors. In react, there is 'useEffect' could do this, but in other framework, there is no such thing, so how could I do? |
usually with a thunk: const getPost = createAsyncThunk(
"posts/getPost",
async (id: string, { getState }): Promise<Post> => {
const cachedPost = selectPostById(getState(), id);
if (cachedPost) return cachedPost;
return fetch('api/post/' + id).then(r => r.json());
}
);
const postAdapter = createEntityAdapter<Post>();
const { selectById: selectPostById } = postAdapter.getSelectors(
(state: RootState) => state.posts
);
const postSlice = createSlice({
name: "posts",
initialState: postAdapter.getInitialState({
loading: {} as Record<string, string>,
}),
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getPost.pending, (state, action) => {
state.loading[action.meta.arg] = action.meta.requestId;
})
.addCase(getPost.rejected, (state, action) => {
if (state.loading[action.meta.arg] === action.meta.requestId) {
delete state.loading[action.meta.arg];
}
})
.addCase(getPost.fulfilled, (state, action) => {
if (state.loading[action.meta.arg] === action.meta.requestId) {
delete state.loading[action.meta.arg];
}
postAdapter.setOne(state, action);
});
},
});
// then later
const myPost = await dispatch(getPost(id)).unwrap(); This is obviously a lot of code, which is why we recommend using RTK Query, which handles all of this caching and loading state logic for you. It's most commonly used with React hooks, but can be used without. const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: "api/" }),
endpoints: (build) => ({
getPost: build.query<Post, string>({
query: (id) => "post/" + id,
}),
}),
});
// then
const resultPromise = dispatch(api.endpoints.getPost.initiate(id));
try {
const post = await resultPromise.unwrap();
} catch (e) {
console.error(e);
} finally {
resultPromise.unsubscribe();
} |
Appreciate for your patient reply. I've gotton what your said, what you showed above is a 'pull' mode, which means, if I want to use it, I should call a get thunk manually. But actually what I want is a subscribe mode. Which means, when a value in store changed, the thunk should be auto called like a 'effect'. I've gotton an idea inspired by your reply, but don't know if it is acceptable:
|
subscription management is exactly what RTKQ does - when you call the initiate thunk (or the hooks) you create a subscription to that data. when there are no subscriptions left to a cache entry, a timer is set and it's removed if there are still no subscriptions to that at the end of the timer. RTKQ also provides a selector factory so you can use regular selector patterns once a subscription is in place. it's hard to advise specifically without knowledge of what sort of library you're trying to work with. const getPost = async (id, callback) => {
const selectPostEntry = api.endpoints.getPost.select(id);
const rtkqSubscription = store.dispatch(api.endpoints.getPost.initiate(id));
await rtkqSubscription;
let lastPostEntry = undefined;
const unsubscribeStore = store.subscribe(() => {
const data = selectPostEntry(store.getState());
if (lastPostEntry !== data) {
callback(data);
lastPostEntry = data;
}
});
return () => {
rtkqSubscription.unsubscribe();
unsubscribeStore();
}
}
const unsubscribe = getPost(1, (postEntry) => doSomething(postEntry.data)) |
@dhlolo : the best answer is to have a component that reads a value from the store via The second best option is to use the RTK listener middleware and watch for state changes, then have it dispatch the query thunk for the endpoint. |
Thanks for your advice. Actually, I am working with a non-standard webview env in wechat app called
And you have to claim component both in js and xml:
|
My store has 2 points and I want to calculate their distance through the google maps api.
I have been suggested there to use reselect since the distance is a resulting data from the store state.
I am ready to go. But now I wonder how to handle asynchronous calculation with reselect:
But since the route calculation is asynchronous, I am wondering how to deal with it.
Should my method return a promise that will wait for resolution? The docs doesn't mention anything about it.
The text was updated successfully, but these errors were encountered: