I’m getting a user from my API and store it in my state so I don’t have to fetch it again. Problem is that multiple ponents requests the user at the same time resulting in multiple concurrent fetch requests.
Is there a good pattern to avoid this?
This is my saga
function* watchUserRequests() {
yield takeEvery(actionTypes.USER_REQUESTED, userRequested);
}
function* userRequested(action) {
const {id} = action.payload;
let user = yield select(state => state.users.all[id]);
// cancel if user exists
if (user) return;
user = yield call(userApi.get, id);
yield put(userActions.userLoaded(id, banner));
}
Actions
export function userRequested(id) {
return {type: types.USER_REQUESTED, payload: {id}};
}
export function userLoaded(id, user) {
return {type: types.USER_LOADED, payload: {id, user}};
}
I’m getting a user from my API and store it in my state so I don’t have to fetch it again. Problem is that multiple ponents requests the user at the same time resulting in multiple concurrent fetch requests.
Is there a good pattern to avoid this?
This is my saga
function* watchUserRequests() {
yield takeEvery(actionTypes.USER_REQUESTED, userRequested);
}
function* userRequested(action) {
const {id} = action.payload;
let user = yield select(state => state.users.all[id]);
// cancel if user exists
if (user) return;
user = yield call(userApi.get, id);
yield put(userActions.userLoaded(id, banner));
}
Actions
export function userRequested(id) {
return {type: types.USER_REQUESTED, payload: {id}};
}
export function userLoaded(id, user) {
return {type: types.USER_LOADED, payload: {id, user}};
}
Share
Improve this question
edited Apr 27, 2021 at 7:10
Lin Du
103k136 gold badges334 silver badges567 bronze badges
asked Aug 8, 2017 at 15:25
FredrikFredrik
411 silver badge5 bronze badges
6
- To avoid the concurrent fetch requests you can store in state not only fetched users but also those being fetched. I can see how that is not pretty but can't think of a way to avoid that. You could write some higher order function to at least isolate some of the logic. – Martin Kadlec Commented Aug 8, 2017 at 17:33
- Thank you, that is a good idea. I tried to dispatch a USER_LOADING action at the start of userRequested function to store this information. But all the USER_REQUESTED action were faster and already on the way before the first USER_LOADING action reached the reducer. – Fredrik Commented Aug 8, 2017 at 20:16
- You will have to change the state in the reducer on userRequested action. – Martin Kadlec Commented Aug 8, 2017 at 20:20
-
Did you consider using
takeLatest
instead oftakeEvery
? – Rowland Commented Aug 15, 2017 at 8:28 - I still need every request when different ids are requested. What i did finally was to preload every user I needed before the ponents started to request them. – Fredrik Commented Aug 16, 2017 at 11:51
2 Answers
Reset to default 3It think you are looking for something like
import {
take,
fork,
cancel,
} from 'redux-saga';
const takeLatestDeep = (pattern, identifier, fn, ...args) => fork(function* () {
const tasks = {};
while(true) {
const action = yield take(pattern);
const id = identifier(action);
if (tasks[id]) {
yield cancel(tasks[id]);
}
tasks[id] = yield fork(fn, ...args.concat(action));
}
})
That you can use like this
yield takeLatestDeep(
actionTypes.USER_REQUESTED,
action => action.payload.id,
fetchLegacyRecipientsSaga,
...args
);
This is how I solved this problem in another saga. The "type" here can be ignored
- Accumulate the requested id in an ids object:
- Dispatch action to fetch all accumulated ids
- Debounce fetch for 50 milliseconds
- While waiting... Add new Ids to the ids object
- While waiting... Cancel fetch task to give you 50 new milliseconds
- Commit fetch
- Clear tasks and ids
Code:
let ids = {};
let tasks = {};
function* watchCounterRequests() {
yield takeEvery(actionTypes.COUNTER_REQUESTED, accumulate);
}
function* watchCounterFetchRequests() {
yield takeEvery(actionTypes.COUNTER_FETCH_REQUESTED, counterFetchRequested);
}
function* accumulate(action) {
const {id, type} = action.payload;
if (!ids[type]) ids[type] = {};
ids[type][id] = true;
if (tasks[type]) {
yield cancel(tasks[type]);
}
tasks[type] = yield fork(fetchCounters, type);
}
function* fetchCounters(type) {
yield call(delay, 50);
yield put({
type: actionTypes.COUNTER_FETCH_REQUESTED,
payload: {type: type, ids: Object.keys(ids[type])},
});
delete ids[type];
delete tasks[type];
}
function* counterFetchRequested(action) {
const {ids, type} = action.payload;
let typeName = type + 'Ids';
let req = {
[typeName]: ids
};
yield put(counterActions.loadCounters(req));
}
export default [watchCounterRequests, watchCounterFetchRequests];
Most of this es from here: https://marmelab./blog/2016/10/18/using-redux-saga-to-deduplicate-and-group-actions.html