Reputation: 588
I'm developing app with (redux + redux-saga + typescript + typesafe-actions + ducks)
I'm considering how to implement loading indicator during api call.
this is my structure
state
ducks
common
actions.ts
reducers.ts
sagas.ts
selectors.ts
group
same as common/
user
same as common/
common/actions
export const beginLoading = () => action(CommonActionTypes.BEGIN_LOADING);
export const endLoading = () => action(CommonActionTypes.END_LOADING);
group/sagas
export function* handleFetchGroup() {
try {
const { group } = yield call(
Api.fetchGroup
);
yield put(addGroup(group));
} catch (err) {
console.error(err);
}
}
user/sagas
export function* handleFetchUsers(action: addGroup) {
try {
yield take(ADD_GROUP);
const users = yield call(
Api.fetchUsers,
action.payload.id
);
yield put(addUsers(users));
} catch (err) {
console.error(err);
}
}
At first, I tried this.
group/sagas
export function* handleFetchGroup() {
try {
yield put(beginLoading()); // added
const { group } = yield call(
Api.fetchGroup
);
for(const group of groups) {
yield put(addGroup(group));
}
yield put(endLoading()); // added
} catch (err) {
yield put(endLoading());
console.error(err);
}
}
in this case, the loading indicator shows only during fetching group data.
so I tried this.
export function* handleFetchGroup() {
try {
yield put(beginLoading());
const { groups } = yield call(
Api.fetchGroups
);
for(const group of groups) {
yield put(addGroup(group));
}
} catch (err) {
yield put(endLoading());
console.error(err);
}
}
user/sagas
export function* handleFetchUsers(action: addGroup) {
try {
yield take(ADD_GROUP);
const users = yield call(
Api.fetchUsers,
action.payload.id
);
yield put(addUsers(users));
yield put(endLoading()); // added
} catch (err) {
console.error(err);
yield put(endLoading());
}
}
in this case,the loading indicator disappeared when the first fetchUsers finished.
Finally I tried this.
export function* handleFetchGroup() {
try {
yield put(beginLoading());
const { groups } = yield call(
Api.fetchGroups
);
for(const group of groups) {
const users = yield call(Api.fetchUsers, group.id); // add
yield put(addUsers(users));
}
yield put(endLoading());
} catch (err) {
yield put(endLoading());
console.error(err);
}
}
But in this case, group saga depends on user entity. So I want to avoid this, but I didn't come up with it.
Do you have any solution??
Upvotes: 1
Views: 4900
Reputation: 11848
I suggest to have isLoading
property in user state and group state. When action beginLoading
for group is emitted (I assume, that this action will trigger handleFetchGroup
), set isLoading: true
in group reducer. When group loading is finished, set isLoading: false
in group reducer.
Exactly the same logic implement for user. So isLoading
which exist in user reducer will be set in response to beginLoading
for user and so on.
In component, which will display loading indicator have isLoading
from group and isLoading
from user and show loading indicator whenever either of two is true.
For example (not tested, use as hint)
const LoadingIndicator: FC<{ user: UserState, group: GroupState }> = ({group, user}) => (
group.isLoading || user.isLoading ? <div>Loading...</div> : null
}
export default connect(state => ({ user: state.user, group: state.group }), {})(LoadingIndicator)
I also see that you're triggering handleFetchUsers
in response to ADD_GROUP
. This also better to decouple. You may consider creating additional saga (for example loadAll
) which will trigger user load in response to group load finish.
For example (not tested)
function *loadAll() {
yield put(BEGIN_GROUP_LOAD)
yield takeEvery(GROUP_LOAD_FINISHED, function*(action) => {
yield handleFetchUsers(action)
}
}
Upvotes: 1