4

I am developing with aurelia and using aurelia-store for app state management. On loading data from server, I want to change an isLoading field true/false to show mask on related components. So I have defined a property in my state isLoading (for example). In the loading action I want first change the loading state to true and after data retrieved to false. So that according to the value of this field (isLoading) I want to display the mask over component.

I want something like this:

export async function getRoles(state) {
  try {
    return Object.assign({}, state, { isRolesListLoading: {busy: true} });
    const getRoles = await accountManagement.getRoles();
    return Object.assign({}, state, { getRoles, isRolesListLoading: {busy: false} });

  } catch (error) {
    console.log('error getRoles "error": ', error);
  }
}

but as I have figured from aurelia documentations, two state changes are not permitted in one action.

What should I do?

And I have an idea to dispatch another action in this action first to make isLoading true and then do the job. Something like this:

export async function getRoles(state) {
  try {
    desiredDispatch('goToLoadingState'); // fake code
    const getRoles = await accountManagement.getRoles();
    return Object.assign({}, state, { getRoles, isRolesListLoading: {busy: false} });

  } catch (error) {
    console.log('error getRoles "error": ', error);
  }
}

But I can't find some documentation on how to dispatch another action in one action.

What are the possible solutions?

Mohammad RN
  • 131
  • 10

1 Answers1

5

I've tried to dummy down your question to a small sample, which you can find over here.

The initialState is like this:

initialState: {
  roles: [],
  isLoading: false
}

As you can see it has a roles array, where the to-be-loaded roles should be stored into and an isLoading boolean to conditionally show a loading indicator.

Now that we've setup the sample lets dive into the details.

First of all loading data remotely from within an action is doable but should be done with care. The dispatch pipeline of Aurelia Store is an async queue. That means new actions will automatically be queued at the end. Now if the action currently executed takes long to resolve, you'll might experience issues with lagging UI etc, since all subsequent actions only get updated later.

Second, an action is supposed to create one new state. What you would want to do actually would consist of 3 actions.

  1. turn on the loading indicator
  2. load data and update the store
  3. turn off the loading indicator

So as in linked example, I'd propose to do it the following way:

export class App {
  ...

  async loadRoles() {
    // Activate the loader, await the action so you don't start loading before the new state is actually set
    await this.store.dispatch(setLoader, true);
    // Local to the function start loading the roles, so you don't block the action queue
    const roles = await loadRoles();
    // once the data is available update the roles
    await this.store.dispatch(updateRoles, roles);
    // once that is set disable the loader
    await this.store.dispatch(setLoader, false);
  }
}

async function loadRoles() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(["User role", "Admin role", "Superuser role"]);
    }, 1000);
  });
}

function setLoader(state, isLoading) {
  return Object.assign({}, state, { isLoading });
}

function updateRoles(state, roles) {
  return Object.assign({}, state, { roles });
}

Now those 3 dispatches could also be reduced to 2, since setting data plus disabling the loader could go in one shot. The nice thing about actions is that you can create a new function which does exactly that by composing a new out of the old two.

function updateRolesAndDisableLoader(state, roles) {
  return Object.assign(
    {},
    updateRoles(state, roles),
    setLoader(state, false)
  );
}
zewa666
  • 2,593
  • 17
  • 20