yn1043
yn1043

Reputation: 588

What is the best way to implement Loading indicator in redux-saga app?

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

Answers (1)

Fyodor Yemelyanenko
Fyodor Yemelyanenko

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

Related Questions