diff --git a/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seat_details.vue b/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seat_details.vue
index dc5e53e688f5c536d5dbb338ea1f73846615c017..ed3bc028afccc066ed89d18fc14940c3801ad350 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seat_details.vue
+++ b/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seat_details.vue
@@ -1,6 +1,6 @@
 <script>
 import { GlTable, GlBadge, GlLink } from '@gitlab/ui';
-import { mapActions, mapGetters } from 'vuex';
+import { mapState, mapActions } from 'vuex';
 import { formatDate } from '~/lib/utils/datetime_utility';
 import { DETAILS_FIELDS } from '../constants';
 import SubscriptionSeatDetailsLoader from './subscription_seat_details_loader.vue';
@@ -20,15 +20,16 @@ export default {
     },
   },
   computed: {
-    ...mapGetters(['membershipsById']),
-    state() {
-      return this.membershipsById(this.seatMemberId);
-    },
+    ...mapState({
+      userDetailsEntry(state) {
+        return state.userDetails[this.seatMemberId];
+      },
+    }),
     items() {
-      return this.state.items;
+      return this.userDetailsEntry.items;
     },
-    isLoading() {
-      return this.state.isLoading;
+    isLoaderShown() {
+      return this.userDetailsEntry.isLoading || this.userDetailsEntry.hasError;
     },
   },
   created() {
@@ -43,7 +44,7 @@ export default {
 </script>
 
 <template>
-  <div v-if="isLoading">
+  <div v-if="isLoaderShown">
     <subscription-seat-details-loader />
   </div>
   <gl-table v-else :fields="$options.fields" :items="items" data-testid="seat-usage-details">
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seats.vue b/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seats.vue
index ea21386f3eff9c73f1737cbc54069aba5ae131a6..16e0bfc28bd6021b07218cf1cb13e6e5a439c2e7 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seats.vue
+++ b/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_seats.vue
@@ -12,6 +12,7 @@ import {
   GlTable,
   GlTooltipDirective,
   GlToggle,
+  GlSkeletonLoader,
 } from '@gitlab/ui';
 import { mapActions, mapState, mapGetters } from 'vuex';
 import { helpPagePath } from '~/helpers/help_page_helper';
@@ -58,10 +59,11 @@ export default {
     StatisticsCard,
     StatisticsSeatsCard,
     SubscriptionUpgradeInfoCard,
+    GlSkeletonLoader,
   },
   computed: {
     ...mapState([
-      'isLoading',
+      'hasError',
       'page',
       'perPage',
       'total',
@@ -86,7 +88,7 @@ export default {
       'previewFreeUserCap',
       'activeTrial',
     ]),
-    ...mapGetters(['tableItems']),
+    ...mapGetters(['tableItems', 'isLoading']),
     currentPage: {
       get() {
         return this.page;
@@ -171,6 +173,9 @@ export default {
     hasLimitedPlanOrPreviewLimitedPlan() {
       return this.hasLimitedFreePlan || this.previewFreeUserCap;
     },
+    isLoaderShown() {
+      return this.isLoading || this.hasError;
+    },
   },
   created() {
     this.fetchBillableMembersList();
@@ -180,7 +185,6 @@ export default {
     ...mapActions([
       'fetchBillableMembersList',
       'fetchGitlabSubscription',
-      'resetBillableMembers',
       'setBillableMemberToRemove',
       'changeMembershipState',
       'setSearchQuery',
@@ -217,7 +221,7 @@ export default {
         value: user.membership_state === MEMBER_ACTIVE_STATE,
       };
 
-      if (this.isLoading) {
+      if (this.isLoaderShown) {
         return { ...props, disabled: true };
       }
 
@@ -316,30 +320,52 @@ export default {
     >
       {{ pendingMembersAlertMessage }}
     </gl-alert>
-    <div class="gl-bg-gray-10 gl-display-flex gl-sm-flex-direction-column gl-p-5">
-      <statistics-card
-        :help-link="$options.i18n.seatsInUseLink"
-        :help-tooltip="seatsInUseTooltipText"
-        :description="seatsInUseText"
-        :percentage="seatsInUsePercentage"
-        :usage-value="String(totalSeatsInUse)"
-        :total-value="displayedTotalSeats"
-        class="gl-w-full gl-md-w-half gl-md-mr-5"
-      />
 
-      <subscription-upgrade-info-card
-        v-if="showUpgradeInfoCard"
-        :max-namespace-seats="maxFreeNamespaceSeats"
-        :explore-plans-path="explorePlansPath"
-        class="gl-w-full gl-md-w-half gl-md-mt-0 gl-mt-5"
-      />
-      <statistics-seats-card
-        v-else
-        :seats-used="maxSeatsUsed"
-        :seats-owed="seatsOwed"
-        :purchase-button-link="addSeatsHref"
-        class="gl-w-full gl-md-w-half gl-md-mt-0 gl-mt-5"
-      />
+    <div class="gl-bg-gray-10 gl-p-5">
+      <div
+        v-if="isLoaderShown"
+        class="gl-display-grid gl-md-grid-template-columns-2 gl-gap-5"
+        data-testid="skeleton-loader-cards"
+      >
+        <div class="gl-bg-white gl-border gl-p-5 gl-rounded-base">
+          <gl-skeleton-loader :height="64">
+            <rect width="140" height="30" x="5" y="0" rx="4" />
+            <rect width="240" height="10" x="5" y="40" rx="4" />
+            <rect width="340" height="10" x="5" y="54" rx="4" />
+          </gl-skeleton-loader>
+        </div>
+
+        <div class="gl-bg-white gl-border gl-p-5 gl-rounded-base">
+          <gl-skeleton-loader :height="64">
+            <rect width="140" height="30" x="5" y="0" rx="4" />
+            <rect width="240" height="10" x="5" y="40" rx="4" />
+            <rect width="340" height="10" x="5" y="54" rx="4" />
+          </gl-skeleton-loader>
+        </div>
+      </div>
+
+      <div v-else class="gl-display-grid gl-md-grid-template-columns-2 gl-gap-5">
+        <statistics-card
+          :help-link="$options.i18n.seatsInUseLink"
+          :help-tooltip="seatsInUseTooltipText"
+          :description="seatsInUseText"
+          :percentage="seatsInUsePercentage"
+          :usage-value="String(totalSeatsInUse)"
+          :total-value="displayedTotalSeats"
+        />
+
+        <subscription-upgrade-info-card
+          v-if="showUpgradeInfoCard"
+          :max-namespace-seats="maxFreeNamespaceSeats"
+          :explore-plans-path="explorePlansPath"
+        />
+        <statistics-seats-card
+          v-else
+          :seats-used="maxSeatsUsed"
+          :seats-owed="seatsOwed"
+          :purchase-button-link="addSeatsHref"
+        />
+      </div>
     </div>
 
     <div class="gl-bg-gray-10 gl-p-5 gl-display-flex">
@@ -367,7 +393,7 @@ export default {
       class="seats-table"
       :items="tableItems"
       :fields="fields"
-      :busy="isLoading"
+      :busy="isLoaderShown"
       :show-empty="true"
       data-testid="table"
       :empty-text="emptyText"
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/store/actions.js b/ee/app/assets/javascripts/usage_quotas/seats/store/actions.js
index cc9831caa12dcca6ef120f8266c6a5586f932f33..cb0d8ee3c32e816616ada6714a6e8975b71f8f18 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/store/actions.js
+++ b/ee/app/assets/javascripts/usage_quotas/seats/store/actions.js
@@ -48,10 +48,6 @@ export const receiveGitlabSubscriptionError = ({ commit }) => {
   commit(types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR);
 };
 
-export const resetBillableMembers = ({ commit }) => {
-  commit(types.RESET_BILLABLE_MEMBERS);
-};
-
 export const setBillableMemberToRemove = ({ commit }, member) => {
   commit(types.SET_BILLABLE_MEMBER_TO_REMOVE, member);
 };
@@ -64,14 +60,19 @@ export const changeMembershipState = async ({ commit, dispatch, state }, user) =
       user.membership_state === MEMBER_ACTIVE_STATE ? MEMBER_AWAITING_STATE : MEMBER_ACTIVE_STATE;
 
     await GroupsApi.changeMembershipState(state.namespaceId, user.id, newState);
-
-    await dispatch('fetchBillableMembersList');
-    await dispatch('fetchGitlabSubscription');
+    dispatch('changeMembershipStateSuccess');
   } catch {
     dispatch('changeMembershipStateError');
   }
 };
 
+export const changeMembershipStateSuccess = ({ commit, dispatch }) => {
+  dispatch('fetchBillableMembersList');
+  dispatch('fetchGitlabSubscription');
+
+  commit(types.CHANGE_MEMBERSHIP_STATE_SUCCESS);
+};
+
 export const changeMembershipStateError = ({ commit }) => {
   createAlert({
     message: __('Something went wrong. Please try again.'),
@@ -80,7 +81,9 @@ export const changeMembershipStateError = ({ commit }) => {
   commit(types.CHANGE_MEMBERSHIP_STATE_ERROR);
 };
 
-export const removeBillableMember = ({ dispatch, state }) => {
+export const removeBillableMember = ({ dispatch, state, commit }) => {
+  commit(types.REMOVE_BILLABLE_MEMBER);
+
   return GroupsApi.removeBillableMemberFromGroup(state.namespaceId, state.billableMemberToRemove.id)
     .then(() => dispatch('removeBillableMemberSuccess'))
     .catch(() => dispatch('removeBillableMemberError'));
@@ -115,7 +118,7 @@ export const fetchBillableMemberDetails = ({ dispatch, commit, state }, memberId
     return Promise.resolve();
   }
 
-  commit(types.FETCH_BILLABLE_MEMBER_DETAILS, memberId);
+  commit(types.FETCH_BILLABLE_MEMBER_DETAILS, { memberId });
 
   return GroupsApi.fetchBillableGroupMemberMemberships(state.namespaceId, memberId)
     .then(({ data }) =>
@@ -125,7 +128,7 @@ export const fetchBillableMemberDetails = ({ dispatch, commit, state }, memberId
 };
 
 export const fetchBillableMemberDetailsError = ({ commit }, memberId) => {
-  commit(types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, memberId);
+  commit(types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, { memberId });
 
   createAlert({
     message: s__('Billing|An error occurred while getting a billable member details.'),
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/store/getters.js b/ee/app/assets/javascripts/usage_quotas/seats/store/getters.js
index c667ba719f28acb333260ad6062957c8be0af2c1..d5c354afbda954ec930d83e4a897d4f2321897be 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/store/getters.js
+++ b/ee/app/assets/javascripts/usage_quotas/seats/store/getters.js
@@ -8,6 +8,8 @@ export const tableItems = (state) => {
   }));
 };
 
-export const membershipsById = (state) => (memberId) => {
-  return state.userDetails[memberId] || { isLoading: true, items: [] };
-};
+export const isLoading = (state) =>
+  state.isLoadingBillableMembers ||
+  state.isLoadingGitlabSubscription ||
+  state.isChangingMembershipState ||
+  state.isRemovingBillableMember;
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/store/mutation_types.js b/ee/app/assets/javascripts/usage_quotas/seats/store/mutation_types.js
index e3afa1f65db64a4ed46e8a55a543981611bf9bce..8f987ea0bbf0e4d15a5150936c8af03b6ec7b0cf 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/store/mutation_types.js
+++ b/ee/app/assets/javascripts/usage_quotas/seats/store/mutation_types.js
@@ -10,7 +10,6 @@ export const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY';
 export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
 export const SET_SORT_OPTION = 'SET_SORT_OPTION';
 
-export const RESET_BILLABLE_MEMBERS = 'RESET_BILLABLE_MEMBERS';
 export const REMOVE_BILLABLE_MEMBER = 'REMOVE_BILLABLE_MEMBER';
 export const REMOVE_BILLABLE_MEMBER_SUCCESS = 'REMOVE_BILLABLE_MEMBER_SUCCESS';
 export const REMOVE_BILLABLE_MEMBER_ERROR = 'REMOVE_BILLABLE_MEMBER_ERROR';
@@ -21,4 +20,5 @@ export const FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS = 'FETCH_BILLABLE_MEMBER_DETA
 export const FETCH_BILLABLE_MEMBER_DETAILS_ERROR = 'FETCH_BILLABLE_MEMBER_DETAILS_ERROR';
 
 export const CHANGE_MEMBERSHIP_STATE = 'CHANGE_MEMBERSHIP_STATE';
+export const CHANGE_MEMBERSHIP_STATE_SUCCESS = 'CHANGE_MEMBERSHIP_STATE_SUCCESS';
 export const CHANGE_MEMBERSHIP_STATE_ERROR = 'CHANGE_MEMBERSHIP_STATE_ERROR';
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/store/mutations.js b/ee/app/assets/javascripts/usage_quotas/seats/store/mutations.js
index 3c2243019cce0093d2fc0673828053c49ad69bff..ab685e88d46b576db00946725769c97942ee7862 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/store/mutations.js
+++ b/ee/app/assets/javascripts/usage_quotas/seats/store/mutations.js
@@ -7,27 +7,12 @@ import {
 import * as types from './mutation_types';
 
 export default {
-  [types.REQUEST_BILLABLE_MEMBERS](state) {
-    state.isLoading = true;
-    state.hasError = false;
-  },
-
+  // Gitlab subscription
   [types.REQUEST_GITLAB_SUBSCRIPTION](state) {
-    state.isLoading = true;
+    state.isLoadingGitlabSubscription = true;
     state.hasError = false;
   },
 
-  [types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, payload) {
-    const { data, headers } = payload;
-    state.members = data;
-
-    state.total = Number(headers[HEADER_TOTAL_ENTRIES]);
-    state.page = Number(headers[HEADER_PAGE_NUMBER]);
-    state.perPage = Number(headers[HEADER_ITEMS_PER_PAGE]);
-
-    state.isLoading = false;
-  },
-
   [types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, payload) {
     const { usage, plan } = payload;
 
@@ -43,19 +28,15 @@ export default {
       state.hasReachedFreePlanLimit = false;
     }
 
-    state.isLoading = false;
-  },
-
-  [types.RECEIVE_BILLABLE_MEMBERS_ERROR](state) {
-    state.isLoading = false;
-    state.hasError = true;
+    state.isLoadingGitlabSubscription = false;
   },
 
   [types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR](state) {
-    state.isLoading = false;
+    state.isLoadingGitlabSubscription = false;
     state.hasError = true;
   },
 
+  // Search & Sort
   [types.SET_SEARCH_QUERY](state, searchString) {
     state.search = searchString ?? null;
   },
@@ -68,16 +49,44 @@ export default {
     state.sort = sortOption;
   },
 
-  [types.RESET_BILLABLE_MEMBERS](state) {
-    state.members = [];
+  // Membership
+  [types.CHANGE_MEMBERSHIP_STATE](state) {
+    state.isChangingMembershipState = true;
+    state.hasError = false;
+  },
+
+  [types.CHANGE_MEMBERSHIP_STATE_SUCCESS](state) {
+    state.isChangingMembershipState = false;
+  },
+
+  [types.CHANGE_MEMBERSHIP_STATE_ERROR](state) {
+    state.isChangingMembershipState = false;
+    state.hasError = true;
+  },
+
+  // Billable member list
+  [types.REQUEST_BILLABLE_MEMBERS](state) {
+    state.isLoadingBillableMembers = true;
+    state.hasError = false;
+  },
+
+  [types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, payload) {
+    const { data, headers } = payload;
+    state.members = data;
+
+    state.total = Number(headers[HEADER_TOTAL_ENTRIES]);
+    state.page = Number(headers[HEADER_PAGE_NUMBER]);
+    state.perPage = Number(headers[HEADER_ITEMS_PER_PAGE]);
 
-    state.total = null;
-    state.page = null;
-    state.perPage = null;
+    state.isLoadingBillableMembers = false;
+  },
 
-    state.isLoading = false;
+  [types.RECEIVE_BILLABLE_MEMBERS_ERROR](state) {
+    state.isLoadingBillableMembers = false;
+    state.hasError = true;
   },
 
+  // Billable member removal
   [types.SET_BILLABLE_MEMBER_TO_REMOVE](state, memberToRemove) {
     if (!memberToRemove) {
       state.billableMemberToRemove = null;
@@ -88,33 +97,22 @@ export default {
     }
   },
 
-  [types.CHANGE_MEMBERSHIP_STATE](state) {
-    state.isLoading = true;
-    state.hasError = false;
-  },
-
-  [types.CHANGE_MEMBERSHIP_STATE_ERROR](state) {
-    state.isLoading = false;
-    state.hasError = true;
-  },
-
   [types.REMOVE_BILLABLE_MEMBER](state) {
-    state.isLoading = true;
+    state.isRemovingBillableMember = true;
     state.hasError = false;
   },
 
   [types.REMOVE_BILLABLE_MEMBER_SUCCESS](state) {
-    state.isLoading = false;
-    state.hasError = false;
+    state.isRemovingBillableMember = false;
     state.billableMemberToRemove = null;
   },
 
   [types.REMOVE_BILLABLE_MEMBER_ERROR](state) {
-    state.isLoading = false;
-    state.hasError = true;
+    state.isRemovingBillableMember = false;
     state.billableMemberToRemove = null;
   },
 
+  // Billable member details
   [types.FETCH_BILLABLE_MEMBER_DETAILS](state, { memberId }) {
     Vue.set(state.userDetails, memberId, {
       isLoading: true,
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/store/state.js b/ee/app/assets/javascripts/usage_quotas/seats/store/state.js
index c1888bd397eb4b412e676c09d5c64c6a321472b3..065be1b167302d6e2b4fe978bfdacd9f0a68bd68 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/store/state.js
+++ b/ee/app/assets/javascripts/usage_quotas/seats/store/state.js
@@ -11,7 +11,10 @@ export default ({
   freeUserCapEnabled = false,
   previewFreeUserCap = false,
 } = {}) => ({
-  isLoading: false,
+  isLoadingBillableMembers: false,
+  isLoadingGitlabSubscription: false,
+  isChangingMembershipState: false,
+  isRemovingBillableMember: false,
   hasError: false,
   namespaceId,
   namespaceName,
diff --git a/ee/spec/frontend/usage_quotas/seats/components/subscription_seat_details_spec.js b/ee/spec/frontend/usage_quotas/seats/components/subscription_seat_details_spec.js
index fcd13004cfb6f5c12367843adcb349f07ee5fef5..12be76878d5c0a5d118f5bc04438f4a36ed7e958 100644
--- a/ee/spec/frontend/usage_quotas/seats/components/subscription_seat_details_spec.js
+++ b/ee/spec/frontend/usage_quotas/seats/components/subscription_seat_details_spec.js
@@ -2,7 +2,6 @@ import { GlTable } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
 import Vue from 'vue';
 import Vuex from 'vuex';
-import Api from 'ee/api';
 import SubscriptionSeatDetails from 'ee/usage_quotas/seats/components/subscription_seat_details.vue';
 import SubscriptionSeatDetailsLoader from 'ee/usage_quotas/seats/components/subscription_seat_details_loader.vue';
 import createStore from 'ee/usage_quotas/seats/store';
@@ -18,12 +17,24 @@ describe('SubscriptionSeatDetails', () => {
     fetchBillableMemberDetails: jest.fn(),
   };
 
-  const createComponent = () => {
-    const store = createStore(initState({ namespaceId: 1, isLoading: true }));
+  const createComponent = ({ initialUserDetails } = { initialUserDetails: {} }) => {
+    const seatMemberId = 1;
+    const store = createStore(initState({ namespaceId: 1 }));
+    store.state = {
+      ...store.state,
+      userDetails: {
+        [seatMemberId]: {
+          isLoading: false,
+          hasError: false,
+          items: mockMemberDetails,
+          ...initialUserDetails,
+        },
+      },
+    };
 
     wrapper = shallowMount(SubscriptionSeatDetails, {
       propsData: {
-        seatMemberId: 1,
+        seatMemberId,
       },
       store: new Vuex.Store({ ...store, actions }),
       stubs: {
@@ -32,21 +43,43 @@ describe('SubscriptionSeatDetails', () => {
     });
   };
 
-  beforeEach(() => {
-    Api.fetchBillableGroupMemberMemberships = jest.fn(() =>
-      Promise.resolve({ data: mockMemberDetails }),
-    );
-    createComponent();
-  });
-
   afterEach(() => {
     wrapper.destroy();
   });
 
   describe('on created', () => {
+    beforeEach(() => {
+      createComponent();
+    });
+
     it('calls fetchBillableMemberDetails', () => {
       expect(actions.fetchBillableMemberDetails).toHaveBeenCalledWith(expect.any(Object), 1);
     });
+  });
+
+  describe('loading state', () => {
+    beforeEach(() => {
+      createComponent({
+        initialUserDetails: {
+          isLoading: true,
+        },
+      });
+    });
+
+    it('displays skeleton loader', () => {
+      expect(wrapper.findComponent(SubscriptionSeatDetailsLoader).isVisible()).toBe(true);
+    });
+  });
+
+  describe('error state', () => {
+    beforeEach(() => {
+      createComponent({
+        initialUserDetails: {
+          isLoading: false,
+          hasError: true,
+        },
+      });
+    });
 
     it('displays skeleton loader', () => {
       expect(wrapper.findComponent(SubscriptionSeatDetailsLoader).isVisible()).toBe(true);
diff --git a/ee/spec/frontend/usage_quotas/seats/components/subscription_seats_spec.js b/ee/spec/frontend/usage_quotas/seats/components/subscription_seats_spec.js
index 13b752797ae6792075679d278f8eb4db39c2c049..88344850f7d13947798aaf31d50e4dd21adcf5e9 100644
--- a/ee/spec/frontend/usage_quotas/seats/components/subscription_seats_spec.js
+++ b/ee/spec/frontend/usage_quotas/seats/components/subscription_seats_spec.js
@@ -27,7 +27,6 @@ Vue.use(Vuex);
 const actionSpies = {
   fetchBillableMembersList: jest.fn(),
   fetchGitlabSubscription: jest.fn(),
-  resetBillableMembers: jest.fn(),
   setBillableMemberToRemove: jest.fn(),
   setSearchQuery: jest.fn(),
   changeMembershipState: jest.fn(),
@@ -50,10 +49,10 @@ const fakeStore = ({ initialState, initialGetters }) =>
     actions: actionSpies,
     getters: {
       tableItems: () => mockTableItems,
+      isLoading: () => false,
       ...initialGetters,
     },
     state: {
-      isLoading: false,
       hasError: false,
       namespaceId: 1,
       members: [...mockDataSeats.data],
@@ -95,6 +94,7 @@ describe('Subscription Seats', () => {
   const findStatisticsCard = () => wrapper.findComponent(StatisticsCard);
   const findStatisticsSeatsCard = () => wrapper.findComponent(StatisticsSeatsCard);
   const findSubscriptionUpgradeCard = () => wrapper.findComponent(SubscriptionUpgradeInfoCard);
+  const findSkeletonLoaderCards = () => wrapper.findByTestId('skeleton-loader-cards');
 
   const serializeUser = (rowWrapper) => {
     const avatarLink = rowWrapper.findComponent(GlAvatarLink);
@@ -144,15 +144,15 @@ describe('Subscription Seats', () => {
     return tableWrapper.findAll('tbody tr').wrappers.map(serializeTableRow);
   };
 
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
   describe('actions', () => {
     beforeEach(() => {
       wrapper = createComponent();
     });
 
-    afterEach(() => {
-      wrapper.destroy();
-    });
-
     it('correct actions are called on create', () => {
       expect(actionSpies.fetchBillableMembersList).toHaveBeenCalled();
     });
@@ -168,10 +168,6 @@ describe('Subscription Seats', () => {
       });
     });
 
-    afterEach(() => {
-      wrapper.destroy();
-    });
-
     describe('export button', () => {
       it('has the correct href', () => {
         expect(findExportButton().attributes().href).toBe(providedFields.seatUsageExportPath);
@@ -340,17 +336,21 @@ describe('Subscription Seats', () => {
             });
           });
 
-          it('disables the toggles when isLoading=true', () => {
+          it.each([
+            [true, false],
+            [false, true],
+          ])('disables the toggles when isLoading=%s and hasError=%s', (isLoading, hasError) => {
             wrapper = createComponent({
               mountFn: mount,
               initialGetters: {
                 tableItems: () => mockTableItems,
+                isLoading: () => isLoading,
               },
               initialState: {
-                isLoading: true,
                 hasNoSubscription: true,
                 hasLimitedFreePlan: true,
                 hasReachedFreePlanLimit: true,
+                hasError,
               },
             });
 
@@ -687,17 +687,37 @@ describe('Subscription Seats', () => {
     });
   });
 
-  describe('is loading', () => {
-    beforeEach(() => {
-      wrapper = createComponent({ initialState: { isLoading: true } });
-    });
+  describe('Loading state', () => {
+    describe('When nothing is loading', () => {
+      beforeEach(() => {
+        wrapper = createComponent();
+      });
 
-    afterEach(() => {
-      wrapper.destroy();
+      it('displays the table in a non-busy state', () => {
+        expect(findTable().attributes('busy')).toBe(undefined);
+      });
     });
 
-    it('displays table in loading state', () => {
-      expect(findTable().attributes('busy')).toBe('true');
+    describe.each([
+      [true, false],
+      [false, true],
+    ])('Busy when isLoading=%s and hasError=%s', (isLoading, hasError) => {
+      beforeEach(() => {
+        wrapper = createComponent({
+          initialGetters: { isLoading: () => isLoading },
+          initialState: { hasError },
+        });
+      });
+
+      it('displays loading skeletons instead of statistics cards', () => {
+        expect(findSkeletonLoaderCards().exists()).toBe(true);
+        expect(findStatisticsCard().exists()).toBe(false);
+        expect(findStatisticsSeatsCard().exists()).toBe(false);
+      });
+
+      it('displays table in busy state', () => {
+        expect(findTable().attributes('busy')).toBe('true');
+      });
     });
   });
 
diff --git a/ee/spec/frontend/usage_quotas/seats/store/actions_spec.js b/ee/spec/frontend/usage_quotas/seats/store/actions_spec.js
index 4a0bfa790859f35a77de6c3193320a54068e1cec..449184124d7553f9474b1a862f2b54c9a44a9ade 100644
--- a/ee/spec/frontend/usage_quotas/seats/store/actions_spec.js
+++ b/ee/spec/frontend/usage_quotas/seats/store/actions_spec.js
@@ -1,4 +1,3 @@
-import MockAdapter from 'axios-mock-adapter';
 import * as GroupsApi from 'ee/api/groups_api';
 import Api from 'ee/api';
 import * as actions from 'ee/usage_quotas/seats/store/actions';
@@ -12,26 +11,19 @@ import {
 } from 'ee_jest/usage_quotas/seats/mock_data';
 import testAction from 'helpers/vuex_action_helper';
 import { createAlert, VARIANT_SUCCESS } from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import httpStatusCodes from '~/lib/utils/http_status';
 
+jest.mock('ee/api/groups_api');
+jest.mock('ee/api');
 jest.mock('~/flash');
 
-describe('seats actions', () => {
+describe('Usage Quotas Seats actions', () => {
   let state;
-  let mock;
 
   beforeEach(() => {
     state = State();
-    mock = new MockAdapter(axios);
-  });
-
-  afterEach(() => {
-    mock.reset();
   });
 
   describe('fetchBillableMembersList', () => {
-    let spy;
     const payload = {
       page: 5,
       search: 'search string',
@@ -40,8 +32,6 @@ describe('seats actions', () => {
     };
 
     beforeEach(() => {
-      gon.api_version = 'v4';
-
       state = Object.assign(state, {
         namespaceId: 1,
         page: 5,
@@ -52,7 +42,10 @@ describe('seats actions', () => {
         hasNoSubscription: false,
       });
 
-      spy = jest.spyOn(GroupsApi, 'fetchBillableGroupMembersList');
+      GroupsApi.fetchBillableGroupMembersList.mockResolvedValue({
+        data: mockDataSeats.data,
+        headers: mockDataSeats.headers,
+      });
     });
 
     it('passes correct arguments to Api call', () => {
@@ -64,7 +57,10 @@ describe('seats actions', () => {
         expectedActions: expect.anything(),
       });
 
-      expect(spy).toBeCalledWith(state.namespaceId, expect.objectContaining(payload));
+      expect(GroupsApi.fetchBillableGroupMembersList).toBeCalledWith(
+        state.namespaceId,
+        expect.objectContaining(payload),
+      );
     });
 
     it('queries awaiting members when on limited free plan', () => {
@@ -82,7 +78,7 @@ describe('seats actions', () => {
         expectedActions: expect.anything(),
       });
 
-      expect(spy).toBeCalledWith(
+      expect(GroupsApi.fetchBillableGroupMembersList).toBeCalledWith(
         state.namespaceId,
         expect.objectContaining({ ...payload, include_awaiting_members: true }),
       );
@@ -103,19 +99,13 @@ describe('seats actions', () => {
         expectedActions: expect.anything(),
       });
 
-      expect(spy).toBeCalledWith(
+      expect(GroupsApi.fetchBillableGroupMembersList).toBeCalledWith(
         state.namespaceId,
         expect.objectContaining({ ...payload, include_awaiting_members: true }),
       );
     });
 
     describe('on success', () => {
-      beforeEach(() => {
-        mock
-          .onGet('/api/v4/groups/1/billable_members')
-          .replyOnce(httpStatusCodes.OK, mockDataSeats.data, mockDataSeats.headers);
-      });
-
       it('should dispatch the request and success actions', () => {
         testAction({
           action: actions.fetchBillableMembersList,
@@ -132,11 +122,9 @@ describe('seats actions', () => {
     });
 
     describe('on error', () => {
-      beforeEach(() => {
-        mock.onGet('/api/v4/groups/1/billable_members').replyOnce(httpStatusCodes.NOT_FOUND, {});
-      });
-
       it('should dispatch the request and error actions', () => {
+        GroupsApi.fetchBillableGroupMembersList.mockRejectedValue();
+
         testAction({
           action: actions.fetchBillableMembersList,
           state,
@@ -174,13 +162,11 @@ describe('seats actions', () => {
 
   describe('fetchGitlabSubscription', () => {
     beforeEach(() => {
-      gon.api_version = 'v4';
       state.namespaceId = 1;
+      Api.userSubscription.mockResolvedValue({ data: mockUserSubscription });
     });
 
     it('passes correct arguments to Api call', () => {
-      const spy = jest.spyOn(Api, 'userSubscription');
-
       testAction({
         action: actions.fetchGitlabSubscription,
         state,
@@ -188,16 +174,10 @@ describe('seats actions', () => {
         expectedActions: expect.anything(),
       });
 
-      expect(spy).toBeCalledWith(state.namespaceId);
+      expect(Api.userSubscription).toBeCalledWith(state.namespaceId);
     });
 
     describe('on success', () => {
-      beforeEach(() => {
-        mock
-          .onGet('/api/v4/namespaces/1/gitlab_subscription')
-          .replyOnce(httpStatusCodes.OK, mockUserSubscription);
-      });
-
       it('should dispatch the request and success actions', () => {
         testAction({
           action: actions.fetchGitlabSubscription,
@@ -214,13 +194,9 @@ describe('seats actions', () => {
     });
 
     describe('on error', () => {
-      beforeEach(() => {
-        mock
-          .onGet('/api/v4/namespaces/1/gitlab_subscription')
-          .replyOnce(httpStatusCodes.NOT_FOUND, {});
-      });
-
       it('should dispatch the request and error actions', () => {
+        Api.userSubscription.mockRejectedValue();
+
         testAction({
           action: actions.fetchGitlabSubscription,
           state,
@@ -256,32 +232,21 @@ describe('seats actions', () => {
     });
   });
 
-  describe('resetBillableMembers', () => {
-    it('should commit mutation', () => {
-      testAction({
-        action: actions.resetBillableMembers,
-        state,
-        expectedMutations: [{ type: types.RESET_BILLABLE_MEMBERS }],
-      });
-    });
-  });
-
   describe('setBillableMemberToRemove', () => {
     it('should commit the set member mutation', async () => {
+      const member = { id: 'test' };
+
       await testAction({
         action: actions.setBillableMemberToRemove,
+        payload: member,
         state,
-        expectedMutations: [{ type: types.SET_BILLABLE_MEMBER_TO_REMOVE }],
+        expectedMutations: [{ type: types.SET_BILLABLE_MEMBER_TO_REMOVE, payload: member }],
       });
     });
   });
 
   describe('removeBillableMember', () => {
-    let groupsApiSpy;
-
     beforeEach(() => {
-      groupsApiSpy = jest.spyOn(GroupsApi, 'removeBillableMemberFromGroup');
-
       state = {
         namespaceId: 1,
         billableMemberToRemove: {
@@ -291,36 +256,35 @@ describe('seats actions', () => {
     });
 
     describe('on success', () => {
-      beforeEach(() => {
-        mock.onDelete('/api/v4/groups/1/billable_members/2').reply(httpStatusCodes.OK);
-      });
-
       it('dispatches the removeBillableMemberSuccess action', async () => {
+        GroupsApi.removeBillableMemberFromGroup.mockResolvedValue();
+
         await testAction({
           action: actions.removeBillableMember,
           state,
           expectedActions: [{ type: 'removeBillableMemberSuccess' }],
+          expectedMutations: [{ type: types.REMOVE_BILLABLE_MEMBER }],
         });
 
-        expect(groupsApiSpy).toHaveBeenCalled();
+        expect(GroupsApi.removeBillableMemberFromGroup).toHaveBeenCalledWith(
+          state.namespaceId,
+          state.billableMemberToRemove.id,
+        );
       });
     });
 
     describe('on error', () => {
-      beforeEach(() => {
-        mock
-          .onDelete('/api/v4/groups/1/billable_members/2')
-          .reply(httpStatusCodes.UNPROCESSABLE_ENTITY);
-      });
-
       it('dispatches the removeBillableMemberError action', async () => {
+        GroupsApi.removeBillableMemberFromGroup.mockRejectedValue();
+
         await testAction({
           action: actions.removeBillableMember,
           state,
           expectedActions: [{ type: 'removeBillableMemberError' }],
+          expectedMutations: [{ type: types.REMOVE_BILLABLE_MEMBER }],
         });
 
-        expect(groupsApiSpy).toHaveBeenCalled();
+        expect(GroupsApi.removeBillableMemberFromGroup).toHaveBeenCalled();
       });
     });
   });
@@ -369,31 +333,15 @@ describe('seats actions', () => {
       user = { id: 2, membership_state: MEMBER_ACTIVE_STATE };
     });
 
-    afterEach(() => {
-      mock.reset();
-    });
-
-    describe('on success', () => {
+    describe('Group API call', () => {
       beforeEach(() => {
-        mock
-          .onPut('/api/v4/groups/1/members/2/state')
-          .replyOnce(httpStatusCodes.OK, { success: true });
-
-        expectedActions = [
-          { type: 'fetchBillableMembersList' },
-          { type: 'fetchGitlabSubscription' },
-        ];
-
         expectedMutations = [{ type: types.CHANGE_MEMBERSHIP_STATE }];
+
+        expectedActions = [{ type: 'changeMembershipStateSuccess' }];
       });
 
       describe('for an active user', () => {
         it('passes correct arguments to Api call for an active user', () => {
-          const spy = jest.spyOn(GroupsApi, 'changeMembershipState');
-
-          jest.spyOn(GroupsApi, 'fetchBillableGroupMembersList');
-          jest.spyOn(Api, 'userSubscription');
-
           testAction({
             action: actions.changeMembershipState,
             payload: user,
@@ -402,14 +350,16 @@ describe('seats actions', () => {
             expectedActions,
           });
 
-          expect(spy).toBeCalledWith(state.namespaceId, user.id, MEMBER_AWAITING_STATE);
+          expect(GroupsApi.changeMembershipState).toBeCalledWith(
+            state.namespaceId,
+            user.id,
+            MEMBER_AWAITING_STATE,
+          );
         });
       });
 
       describe('for an awaiting user', () => {
         it('passes correct arguments to Api call for an active user', () => {
-          const spy = jest.spyOn(GroupsApi, 'changeMembershipState');
-
           testAction({
             action: actions.changeMembershipState,
             payload: { ...user, membership_state: MEMBER_AWAITING_STATE },
@@ -418,19 +368,19 @@ describe('seats actions', () => {
             expectedActions,
           });
 
-          expect(spy).toBeCalledWith(state.namespaceId, user.id, MEMBER_ACTIVE_STATE);
+          expect(GroupsApi.changeMembershipState).toBeCalledWith(
+            state.namespaceId,
+            user.id,
+            MEMBER_ACTIVE_STATE,
+          );
         });
       });
     });
 
     describe('on error', () => {
-      beforeEach(() => {
-        mock
-          .onPut('/api/v4/groups/1/members/2/state')
-          .replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY, {});
-      });
-
       it('should dispatch the request and error actions', async () => {
+        GroupsApi.changeMembershipState.mockRejectedValue();
+
         await testAction({
           action: actions.changeMembershipState,
           payload: user,
@@ -442,8 +392,26 @@ describe('seats actions', () => {
     });
   });
 
+  describe('changeMembershipStateSuccess', () => {
+    it('should dispatch billable members list and GitLab subscription', () => {
+      testAction({
+        action: actions.changeMembershipStateSuccess,
+        state,
+        expectedMutations: [
+          {
+            type: types.CHANGE_MEMBERSHIP_STATE_SUCCESS,
+          },
+        ],
+        expectedActions: [
+          { type: 'fetchBillableMembersList' },
+          { type: 'fetchGitlabSubscription' },
+        ],
+      });
+    });
+  });
+
   describe('changeMembershipStateError', () => {
-    it('ccommits mutation and calls createAlert', async () => {
+    it('commits mutation and calls createAlert', async () => {
       await testAction({
         action: actions.changeMembershipStateError,
         state,
@@ -460,9 +428,7 @@ describe('seats actions', () => {
     const member = mockDataSeats.data[0];
 
     beforeAll(() => {
-      GroupsApi.fetchBillableGroupMemberMemberships = jest
-        .fn()
-        .mockResolvedValue({ data: mockMemberDetails });
+      GroupsApi.fetchBillableGroupMemberMemberships.mockResolvedValue({ data: mockMemberDetails });
     });
 
     it('commits fetchBillableMemberDetails', async () => {
@@ -471,7 +437,7 @@ describe('seats actions', () => {
         payload: member.id,
         state,
         expectedMutations: [
-          { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: member.id },
+          { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: { memberId: member.id } },
           {
             type: types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS,
             payload: { memberId: member.id, memberships: mockMemberDetails },
@@ -480,13 +446,13 @@ describe('seats actions', () => {
       });
     });
 
-    it('calls fetchBillableGroupMemberMemberships api', async () => {
+    it('calls fetchBillableGroupMemberMemberships API', async () => {
       await testAction({
         action: actions.fetchBillableMemberDetails,
         payload: member.id,
         state,
         expectedMutations: [
-          { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: member.id },
+          { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: { memberId: member.id } },
           {
             type: types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS,
             payload: { memberId: member.id, memberships: mockMemberDetails },
@@ -497,13 +463,13 @@ describe('seats actions', () => {
       expect(GroupsApi.fetchBillableGroupMemberMemberships).toHaveBeenCalledWith(null, 2);
     });
 
-    it('calls fetchBillableGroupMemberMemberships api only once', async () => {
+    it('calls fetchBillableGroupMemberMemberships API only once', async () => {
       await testAction({
         action: actions.fetchBillableMemberDetails,
         payload: member.id,
         state,
         expectedMutations: [
-          { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: member.id },
+          { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: { memberId: member.id } },
           {
             type: types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS,
             payload: { memberId: member.id, memberships: mockMemberDetails },
@@ -529,34 +495,44 @@ describe('seats actions', () => {
     });
 
     describe('on API error', () => {
-      beforeAll(() => {
-        GroupsApi.fetchBillableGroupMemberMemberships = jest.fn().mockRejectedValue();
-      });
-
       it('dispatches fetchBillableMemberDetailsError', async () => {
+        GroupsApi.fetchBillableGroupMemberMemberships.mockRejectedValue();
+
         await testAction({
-          action: actions.fetchBillableMemberDetailsError,
+          action: actions.fetchBillableMemberDetails,
+          payload: member.id,
           state,
-          expectedMutations: [{ type: types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR }],
+          expectedMutations: [
+            { type: types.FETCH_BILLABLE_MEMBER_DETAILS, payload: { memberId: member.id } },
+          ],
+          expectedActions: [{ type: 'fetchBillableMemberDetailsError', payload: member.id }],
         });
       });
     });
   });
 
   describe('fetchBillableMemberDetailsError', () => {
+    const memberId = 42;
+
     it('commits fetch billable member details error', async () => {
       await testAction({
         action: actions.fetchBillableMemberDetailsError,
+        payload: memberId,
         state,
-        expectedMutations: [{ type: types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR }],
+        expectedMutations: [
+          { type: types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, payload: { memberId } },
+        ],
       });
     });
 
     it('calls createAlert', async () => {
       await testAction({
         action: actions.fetchBillableMemberDetailsError,
+        payload: memberId,
         state,
-        expectedMutations: [{ type: types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR }],
+        expectedMutations: [
+          { type: types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, payload: { memberId } },
+        ],
       });
 
       expect(createAlert).toHaveBeenCalledWith({
diff --git a/ee/spec/frontend/usage_quotas/seats/store/getters_spec.js b/ee/spec/frontend/usage_quotas/seats/store/getters_spec.js
index 91f3cf2352728dffeb7b0170287cf1f932291a60..5ed68ad4220069c0c580499b25f3a73b91e92bf3 100644
--- a/ee/spec/frontend/usage_quotas/seats/store/getters_spec.js
+++ b/ee/spec/frontend/usage_quotas/seats/store/getters_spec.js
@@ -1,12 +1,8 @@
 import * as getters from 'ee/usage_quotas/seats/store/getters';
 import State from 'ee/usage_quotas/seats/store/state';
-import {
-  mockDataSeats,
-  mockTableItems,
-  mockMemberDetails,
-} from 'ee_jest/usage_quotas/seats/mock_data';
+import { mockDataSeats, mockTableItems } from 'ee_jest/usage_quotas/seats/mock_data';
 
-describe('Seat usage table getters', () => {
+describe('Usage Quotas Seats getters', () => {
   let state;
 
   beforeEach(() => {
@@ -27,28 +23,27 @@ describe('Seat usage table getters', () => {
     });
   });
 
-  describe('membershipsById', () => {
-    describe('when data is not availlable', () => {
-      it('returns a base state', () => {
-        expect(getters.membershipsById(state)(0)).toEqual({
-          isLoading: true,
-          items: [],
-        });
-      });
+  describe('isLoading', () => {
+    beforeEach(() => {
+      state.isLoadingBillableMembers = false;
+      state.isLoadingGitlabSubscription = false;
+      state.isChangingMembershipState = false;
+      state.isRemovingBillableMember = false;
     });
 
-    describe('when data is available', () => {
-      it('returns user details state', () => {
-        state.userDetails[0] = {
-          isLoading: false,
-          items: mockMemberDetails,
-        };
-
-        expect(getters.membershipsById(state)(0)).toEqual({
-          isLoading: false,
-          items: mockMemberDetails,
-        });
-      });
+    it('returns false if nothing is being loaded', () => {
+      expect(getters.isLoading(state)).toEqual(false);
+    });
+
+    it.each([
+      'isLoadingBillableMembers',
+      'isLoadingGitlabSubscription',
+      'isChangingMembershipState',
+      'isRemovingBillableMember',
+    ])('returns true if %s is being loaded', (key) => {
+      state[key] = true;
+
+      expect(getters.isLoading(state)).toEqual(true);
     });
   });
 });
diff --git a/ee/spec/frontend/usage_quotas/seats/store/mutations_spec.js b/ee/spec/frontend/usage_quotas/seats/store/mutations_spec.js
index 89c5677ee19ed69ed28ebe09317810694a6875d1..75b6971b5056bdc520252532a7a9dd26c49f9704 100644
--- a/ee/spec/frontend/usage_quotas/seats/store/mutations_spec.js
+++ b/ee/spec/frontend/usage_quotas/seats/store/mutations_spec.js
@@ -7,242 +7,235 @@ import {
   mockUserSubscription,
 } from 'ee_jest/usage_quotas/seats/mock_data';
 
-describe('EE seats module mutations', () => {
+describe('Usage Quotas Seats mutations', () => {
   let state;
 
   beforeEach(() => {
     state = createState();
   });
 
-  describe(types.REQUEST_BILLABLE_MEMBERS, () => {
-    beforeEach(() => {
-      mutations[types.REQUEST_BILLABLE_MEMBERS](state);
-    });
+  describe('GitLab subscription', () => {
+    it(types.REQUEST_GITLAB_SUBSCRIPTION, () => {
+      state.isLoadingGitlabSubscription = false;
+      state.hasError = true;
 
-    it('sets isLoading to true', () => {
-      expect(state.isLoading).toBe(true);
-    });
+      mutations[types.REQUEST_GITLAB_SUBSCRIPTION](state);
 
-    it('sets hasError to false', () => {
+      expect(state.isLoadingGitlabSubscription).toBe(true);
       expect(state.hasError).toBe(false);
     });
-  });
-
-  describe(types.RECEIVE_BILLABLE_MEMBERS_SUCCESS, () => {
-    beforeEach(() => {
-      mutations[types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, mockDataSeats);
-    });
-
-    it('sets state as expected', () => {
-      expect(state.members).toMatchObject(mockDataSeats.data);
-
-      expect(state.total).toBe(3);
-      expect(state.page).toBe(1);
-      expect(state.perPage).toBe(1);
-    });
-
-    it('sets isLoading to false', () => {
-      expect(state.isLoading).toBe(false);
-    });
-  });
-
-  describe(types.RECEIVE_BILLABLE_MEMBERS_ERROR, () => {
-    beforeEach(() => {
-      mutations[types.RECEIVE_BILLABLE_MEMBERS_ERROR](state);
-    });
 
-    it('sets isLoading to false', () => {
-      expect(state.isLoading).toBe(false);
-    });
+    describe(types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS, () => {
+      describe('when subscription data is passed', () => {
+        beforeEach(() => {
+          state.isLoadingGitlabSubscription = true;
 
-    it('sets hasError to true', () => {
-      expect(state.hasError).toBe(true);
-    });
-  });
+          mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, mockUserSubscription);
+        });
 
-  describe(types.REQUEST_GITLAB_SUBSCRIPTION, () => {
-    beforeEach(() => {
-      mutations[types.REQUEST_GITLAB_SUBSCRIPTION](state);
-    });
+        it('sets state as expected', () => {
+          expect(state).toMatchObject({
+            seatsInSubscription: mockUserSubscription.usage.seats_in_subscription,
+            seatsInUse: mockUserSubscription.usage.seats_in_use,
+            maxSeatsUsed: mockUserSubscription.usage.max_seats_used,
+            seatsOwed: mockUserSubscription.usage.seats_owed,
+            hasReachedFreePlanLimit: false,
+            isLoadingGitlabSubscription: false,
+            activeTrial: mockUserSubscription.plan.trial,
+          });
+        });
 
-    it('sets isLoading to true', () => {
-      expect(state.isLoading).toBe(true);
-    });
+        describe('when hasLimitedFreePlan: true', () => {
+          it('sets hasReachedFreePlanLimit to false when limit has not been reached', () => {
+            state = { ...state, hasLimitedFreePlan: true, maxFreeNamespaceSeats: 5 };
 
-    it('sets hasError to false', () => {
-      expect(state.hasError).toBe(false);
-    });
-  });
+            mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {
+              ...mockUserSubscription,
+              usage: { seats_in_use: 4 },
+            });
 
-  describe(types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS, () => {
-    describe('when subscription data is passed', () => {
-      beforeEach(() => {
-        mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, mockUserSubscription);
-      });
+            expect(state.hasReachedFreePlanLimit).toBe(false);
+          });
 
-      it('sets state as expected', () => {
-        expect(state.seatsInSubscription).toBe(mockUserSubscription.usage.seats_in_subscription);
-        expect(state.seatsInUse).toBe(mockUserSubscription.usage.seats_in_use);
-        expect(state.maxSeatsUsed).toBe(mockUserSubscription.usage.max_seats_used);
-        expect(state.seatsOwed).toBe(mockUserSubscription.usage.seats_owed);
-        expect(state.activeTrial).toBe(mockUserSubscription.plan.trial);
-        expect(state.hasReachedFreePlanLimit).toBe(false);
-      });
+          it('sets hasReachedFreePlanLimit to true when limit has been reached', () => {
+            state = { ...state, hasLimitedFreePlan: true, maxFreeNamespaceSeats: 5 };
 
-      describe('when hasLimitedFreePlan: true', () => {
-        it('sets hasReachedFreePlanLimit to false when limit has not been reached', () => {
-          state = { ...state, hasLimitedFreePlan: true, maxFreeNamespaceSeats: 5 };
+            mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {
+              ...mockUserSubscription,
+              usage: { seats_in_use: 5 },
+            });
 
-          mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {
-            ...mockUserSubscription,
-            usage: { seats_in_use: 4 },
+            expect(state.hasReachedFreePlanLimit).toBe(true);
           });
-
-          expect(state.hasReachedFreePlanLimit).toBe(false);
         });
 
-        it('sets hasReachedFreePlanLimit to true when limit has been reached', () => {
-          state = { ...state, hasLimitedFreePlan: true, maxFreeNamespaceSeats: 5 };
+        describe('when plan is on trial', () => {
+          it('sets activeTrial to true', () => {
+            mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {
+              ...mockUserSubscription,
+              plan: { trial: true },
+            });
 
-          mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {
-            ...mockUserSubscription,
-            usage: { seats_in_use: 5 },
+            expect(state.activeTrial).toBe(true);
           });
-
-          expect(state.hasReachedFreePlanLimit).toBe(true);
         });
       });
 
-      describe('when plan is on trial', () => {
-        it('sets activeTrial to true', () => {
-          mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {
-            ...mockUserSubscription,
-            plan: { trial: true },
-          });
+      it('defaults values when subscription data is not passed', () => {
+        state.isLoadingGitlabSubscription = true;
 
-          expect(state.activeTrial).toBe(true);
-        });
-      });
+        mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {});
 
-      it('sets isLoading to false', () => {
-        expect(state.isLoading).toBe(false);
+        expect(state).toMatchObject({
+          seatsInSubscription: 0,
+          seatsInUse: 0,
+          maxSeatsUsed: 0,
+          seatsOwed: 0,
+          isLoadingGitlabSubscription: false,
+          activeTrial: false,
+        });
       });
     });
 
-    describe('when subscription data is not passed', () => {
-      beforeEach(() => {
-        mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {});
-      });
+    it(types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR, () => {
+      state.isLoadingGitlabSubscription = true;
+      state.hasError = false;
 
-      it('sets state as expected', () => {
-        expect(state.seatsInSubscription).toBe(0);
-        expect(state.seatsInUse).toBe(0);
-        expect(state.maxSeatsUsed).toBe(0);
-        expect(state.seatsOwed).toBe(0);
-        expect(state.activeTrial).toBe(false);
-      });
+      mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR](state);
 
-      it('sets isLoading to false', () => {
-        expect(state.isLoading).toBe(false);
-      });
+      expect(state.isLoadingGitlabSubscription).toBe(false);
+      expect(state.hasError).toBe(true);
     });
   });
 
-  describe(types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR, () => {
-    beforeEach(() => {
-      mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR](state);
+  describe('Search and sort', () => {
+    describe(types.SET_SEARCH_QUERY, () => {
+      it('sets the search state', () => {
+        state.search = '';
+        const SEARCH_STRING = 'a search string';
+        mutations[types.SET_SEARCH_QUERY](state, SEARCH_STRING);
+
+        expect(state.search).toBe(SEARCH_STRING);
+      });
+
+      it('sets the search state item to null', () => {
+        state.search = 'a search string';
+        mutations[types.SET_SEARCH_QUERY](state);
+        expect(state.search).toBe(null);
+      });
     });
 
-    it('sets isLoading to false', () => {
-      expect(state.isLoading).toBe(false);
+    it(types.SET_CURRENT_PAGE, () => {
+      state.page = 1;
+      mutations[types.SET_CURRENT_PAGE](state, 42);
+      expect(state.page).toBe(42);
     });
 
-    it('sets hasError to true', () => {
-      expect(state.hasError).toBe(true);
+    it(types.SET_SORT_OPTION, () => {
+      mutations[types.SET_SORT_OPTION](state, 'last_activity_on_desc');
+      expect(state.sort).toBe('last_activity_on_desc');
     });
   });
 
-  describe(types.SET_SEARCH_QUERY, () => {
-    it('sets the search state', () => {
-      const SEARCH_STRING = 'a search string';
-
-      mutations[types.SET_SEARCH_QUERY](state, SEARCH_STRING);
-
-      expect(state.search).toBe(SEARCH_STRING);
+  describe('Changing membership state', () => {
+    it(types.CHANGE_MEMBERSHIP_STATE, () => {
+      state.isChangingMembershipState = false;
+      state.hasError = true;
+      mutations[types.CHANGE_MEMBERSHIP_STATE](state);
+      expect(state).toMatchObject({
+        isChangingMembershipState: true,
+        hasError: false,
+      });
     });
 
-    it('sets the search state item to null', () => {
-      mutations[types.SET_SEARCH_QUERY](state);
+    it(types.CHANGE_MEMBERSHIP_STATE_SUCCESS, () => {
+      state.isChangingMembershipState = true;
+      mutations[types.CHANGE_MEMBERSHIP_STATE_SUCCESS](state);
+      expect(state).toMatchObject({
+        isChangingMembershipState: false,
+      });
+    });
 
-      expect(state.search).toBe(null);
+    it(types.CHANGE_MEMBERSHIP_STATE_ERROR, () => {
+      state.isChangingMembershipState = true;
+      state.hasError = false;
+      mutations[types.CHANGE_MEMBERSHIP_STATE_ERROR](state);
+      expect(state).toMatchObject({
+        isChangingMembershipState: false,
+        hasError: true,
+      });
     });
   });
 
-  describe(types.RESET_BILLABLE_MEMBERS, () => {
-    beforeEach(() => {
-      mutations[types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, mockDataSeats);
-      mutations[types.RESET_BILLABLE_MEMBERS](state);
+  describe('Billable member list', () => {
+    it(types.REQUEST_BILLABLE_MEMBERS, () => {
+      state.isLoadingBillableMembers = false;
+      state.hasError = true;
+      mutations[types.REQUEST_BILLABLE_MEMBERS](state);
+      expect(state).toMatchObject({
+        isLoadingBillableMembers: true,
+        hasError: false,
+      });
     });
 
-    it('resets members state', () => {
-      expect(state.members).toMatchObject([]);
-
-      expect(state.total).toBeNull();
-      expect(state.page).toBeNull();
-      expect(state.perPage).toBeNull();
-
-      expect(state.isLoading).toBe(false);
+    it(types.RECEIVE_BILLABLE_MEMBERS_SUCCESS, () => {
+      state.isLoadingBillableMembers = true;
+      mutations[types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, mockDataSeats);
+      expect(state.members).toMatchObject(mockDataSeats.data);
+      expect(state).toMatchObject({
+        total: 3,
+        page: 1,
+        perPage: 1,
+        isLoadingBillableMembers: false,
+      });
     });
 
-    it('sets isLoading to false', () => {
-      expect(state.isLoading).toBe(false);
+    it(types.RECEIVE_BILLABLE_MEMBERS_ERROR, () => {
+      state.hasError = false;
+      state.isLoadingBillableMembers = true;
+      mutations[types.RECEIVE_BILLABLE_MEMBERS_ERROR](state);
+      expect(state).toMatchObject({
+        isLoadingBillableMembers: false,
+        hasError: true,
+      });
     });
   });
 
-  describe('member removal', () => {
+  describe('Billable member removal', () => {
     const memberToRemove = mockDataSeats.data[0];
 
     beforeEach(() => {
+      state.billableMemberToRemove = { id: 42 };
       mutations[types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, mockDataSeats);
     });
 
-    describe(types.SET_BILLABLE_MEMBER_TO_REMOVE, () => {
-      it('sets the member to remove', () => {
-        mutations[types.SET_BILLABLE_MEMBER_TO_REMOVE](state, memberToRemove);
+    it(types.SET_BILLABLE_MEMBER_TO_REMOVE, () => {
+      mutations[types.SET_BILLABLE_MEMBER_TO_REMOVE](state, memberToRemove);
 
-        expect(state.billableMemberToRemove).toMatchObject(memberToRemove);
-      });
+      expect(state.billableMemberToRemove).toMatchObject(memberToRemove);
     });
 
-    describe(types.REMOVE_BILLABLE_MEMBER, () => {
-      it('sets state to loading', () => {
-        mutations[types.REMOVE_BILLABLE_MEMBER](state, memberToRemove);
+    it(types.REMOVE_BILLABLE_MEMBER, () => {
+      mutations[types.REMOVE_BILLABLE_MEMBER](state, memberToRemove);
 
-        expect(state).toMatchObject({ isLoading: true, hasError: false });
-      });
+      expect(state).toMatchObject({ isRemovingBillableMember: true, hasError: false });
     });
 
-    describe(types.REMOVE_BILLABLE_MEMBER_SUCCESS, () => {
-      it('sets state to successfull', () => {
-        mutations[types.REMOVE_BILLABLE_MEMBER_SUCCESS](state, memberToRemove);
+    it(types.REMOVE_BILLABLE_MEMBER_SUCCESS, () => {
+      mutations[types.REMOVE_BILLABLE_MEMBER_SUCCESS](state, memberToRemove);
 
-        expect(state).toMatchObject({
-          isLoading: false,
-          hasError: false,
-          billableMemberToRemove: null,
-        });
+      expect(state).toMatchObject({
+        isRemovingBillableMember: false,
+        billableMemberToRemove: null,
       });
     });
 
-    describe(types.REMOVE_BILLABLE_MEMBER_ERROR, () => {
-      it('sets state to errored', () => {
-        mutations[types.REMOVE_BILLABLE_MEMBER_ERROR](state, memberToRemove);
+    it(types.REMOVE_BILLABLE_MEMBER_ERROR, () => {
+      mutations[types.REMOVE_BILLABLE_MEMBER_ERROR](state, memberToRemove);
 
-        expect(state).toMatchObject({
-          isLoading: false,
-          hasError: true,
-          billableMemberToRemove: null,
-        });
+      expect(state).toMatchObject({
+        isRemovingBillableMember: false,
+        billableMemberToRemove: null,
       });
     });
   });
@@ -250,57 +243,30 @@ describe('EE seats module mutations', () => {
   describe('fetching billable member details', () => {
     const member = mockDataSeats.data[0];
 
-    describe(types.FETCH_BILLABLE_MEMBER_DETAILS, () => {
-      it('sets the state to loading', () => {
-        mutations[types.FETCH_BILLABLE_MEMBER_DETAILS](state, { memberId: member.id });
-
-        expect(state.userDetails).toMatchObject({
-          [member.id.toString()]: {
-            isLoading: true,
-          },
-        });
-      });
+    beforeEach(() => {
+      delete state.userDetails[member.id];
     });
 
-    describe(types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS, () => {
-      beforeEach(() => {
-        mutations[types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS](state, {
-          memberId: member.id,
-          memberships: mockMemberDetails,
-        });
-      });
-
-      it('sets the state to not loading', () => {
-        expect(state.userDetails[member.id.toString()].isLoading).toBe(false);
-      });
+    it(types.FETCH_BILLABLE_MEMBER_DETAILS, () => {
+      mutations[types.FETCH_BILLABLE_MEMBER_DETAILS](state, { memberId: member.id });
 
-      it('sets the memberships to the state', () => {
-        expect(state.userDetails[member.id.toString()].items).toEqual(mockMemberDetails);
-      });
+      expect(state.userDetails[member.id].isLoading).toBe(true);
     });
 
-    describe(types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, () => {
-      it('sets the state to not loading', () => {
-        mutations[types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR](state, { memberId: member.id });
-
-        expect(state.userDetails[member.id.toString()].isLoading).toBe(false);
+    it(types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS, () => {
+      mutations[types.FETCH_BILLABLE_MEMBER_DETAILS_SUCCESS](state, {
+        memberId: member.id,
+        memberships: mockMemberDetails,
       });
-    });
 
-    describe(types.SET_CURRENT_PAGE, () => {
-      it('sets the page state', () => {
-        mutations[types.SET_CURRENT_PAGE](state, 1);
-
-        expect(state.page).toBe(1);
-      });
+      expect(state.userDetails[member.id].isLoading).toBe(false);
+      expect(state.userDetails[member.id].items).toEqual(mockMemberDetails);
     });
 
-    describe(types.SET_SORT_OPTION, () => {
-      it('sets the sort state', () => {
-        mutations[types.SET_SORT_OPTION](state, 'last_activity_on_desc');
+    it(types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, () => {
+      mutations[types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR](state, { memberId: member.id });
 
-        expect(state.sort).toBe('last_activity_on_desc');
-      });
+      expect(state.userDetails[member.id].isLoading).toBe(false);
     });
   });
 });