Store

Framework7 comes with a built-in lightweight application state management library - Store. It serves as a centralized Store for all the components in an application.

You can use library-specific state management libraries like Vuex for Vue, Redux for React, and use built-in Svelte store functionality. But in case if something simple is required then Framework7 Store can be a good fit.

Create Store

First of all we need to create the store. Let's create separate store.js file for that:

// First import createStore function from Framework7 core
import { createStore } from 'framework7';

// create store
const store = createStore({
  // start with the state (store data)
  state: {
    users: [],
    // ...
  },

  // actions to operate with state and for async manipulations
  actions: {
    // context object containing store state will be passed as an argument
    getUsers({ state }) {
      // fetch users from API
      fetch('some-url')
        .then((res) => res.json())
        .then((users) => {
          // assign new users to store state.users
          state.users = users;
        })
    },
    // ...
  },

  // getters to retrieve the state
  getters: {
    // context object containing store state will be passed as an argument
    users({ state }) {
      return state.users;
    }
  }

})

// export store
export default store;

If you are not using modules, then store.js file will look like:

// save store as global object
window.store = Framework7.createStore({
  state: { /* ... */ },
  actions: { /* ... */ },
  getters: { /* ... */ },
})

In this example we used the following API function:

createStore(storeParameters)- create store

  • storeParameters - object. Object with store parameters

Method returns created store instance

Store Parameters

Now, let look at storeParameters object:

State

state is the single object contains all your application level state and serves as the "single source of truth". This also means usually you will have only one store for each application. A single state tree makes it straightforward to locate a specific piece of state, and allows us to easily take snapshots of the current app state for debugging purposes.

Actions

actions are used to modify the state, for async manipulations, or to call other store actions. Action handlers receive a context object with store state and dispatch method to call other actions. So you can access context.store to access the state, or call other actions with context.dispatch.

As second argument actions handlers may receive any custom data.

To keep store reactive, state modification should be done with assignment. For example:

// modification of current state property - NOT REACTIVE
state.users.push(...users);

// assignemt to new value - REACTIVE
state.users = [...state.users, ...users];

Getters

getters handlers are used to return data from the store state. Also handy when we need to compute derived state based on store state, for example filtering through a list of items:

const store = createStore({
  state: {
    users: [
      { id: 1, name: '...', registered: true },
      { id: 2, name: '...', registered: false }
    ]
  },
  getters: {
    registeredUsers: ({ state }) => {
      return state.users.filter((user) => user.registered);
    }
  }
})

Getter handlers also receive a context object but only with the store state. It is not possible, for example, to call other actions from getters.

Use Store

Now when we created our store, let's find out how to use it.

First of all we need to pass created store to the main App instance:

// import our store
import store from 'path/to/store.js';

const app = new Framework7({
  // pass store to the app's "store" parameter
  store,
  ...
})

Access Store & State

It is possible to access store (and its state) directly by referencing the store instance we created:

import store from 'path/to/store.js';

console.log(store.state.users);

Or by accessing Framework7 instance' store property:

import store from 'path/to/store.js';

const app = new Framework7({
  store,
  ...
})

// somewhere later
console.log(app.store.state.users);

Dispatching Actions

To call an action we need to call store.dispatch method with the name of action to call.

If we have the following store action:

const store = createStore({
  // ...
  actions: {
    // handler receives custom data in second argument
    getUsers({ state }, { total }) {
      fetch(`some-url?total=${total}`)
        .then((res) => res.json())
        .then((users) => {
          state.users = users;
        })
    },
  },
  // ...
})

we have to call store.dispatch method:

import store from 'path/to/store.js';

// call 'getUsers' actions
store.dispatch('getUsers', { total: 10 })

If, in action handler, we want to call another action handler:

const store = createStore({
  // ...
  actions: {
    setLoading({ state }, isLoading) {
      state.isLoading = isLoading;
    },
    // handler context also contains "dispatch" method
    getUsers({ state, dispatch }, { total }) {
      // call other action
      dispatch('setLoading', true);
      fetch(`some-url?total=${total}`)
        .then((res) => res.json())
        .then((users) => {
          state.users = users;
          // call other action
          dispatch('setLoading', false);
        })
    },
  },
  // ...
});

Getters

Getters values can be accessed as static properties of store.getters object.

const store = createStore({
  state: {
    count: 10,
  },
  getters: {
    count({ state }) {
      return state.count;
    },
    double({ state }) {
      return state.count * 2;
    },
  },
});
import store from 'path/to/store.js';

const count = store.getters.count;
const double = store.getters.double;

Getter value is the static object with .value property containing the result of getters handler, so:

console.log(count.value); // -> 10
console.log(double.value); // -> 20

Getters, unlike state, are meant to be reactive. So when you don't need any reactivity you can just access store.state directly, otherwise use getters.

Usage With Router Components

Router component context has $store property with the following properties:

If we have the following store:

const store = createStore({
  state: {
    users: [],
  },
  actions: {
    getUsers({ state }) {
      // ...
    },
  },
  getters: {
    users({ state }) {
      return state.users;
    }
  },
});

Then, for example, we should use the following in Router component:

<template>
  <div class="page">
    <ul>
      <!-- getter has value in ".value" property -->
      ${users.value.map((user) => $h`
        <li>${user.name}</li>
      `)}
    </ul>
  </div>
</template>
<script>
  export default (props, { $store, $on }) => {
    // retrieve "users" getter handler value. Initially empty array
    const users = $store.getters('users');

    $on('pageInit', () => {
      // load users on page init
      $store.dispatch('getUsers');
    });

    return $render;
  }
</script>

Examples

import { createStore } from 'store';

const store = createStore({
  state: {
    loading: false,
    users: [],
  },
  actions: {
    getUsers({ state }) {
      state.loading = true;
      setTimeout(() => {
        state.users = ['User 1', 'User 2', 'User 3', 'User 4', 'User 5'];
        state.loading = false;
      }, 3000);
    },
  },
  getters: {
    loading({ state }) {
      return state.loading;
    },
    users({ state }) {
      return state.users;
    },
  },
});

export default store;
store_page.f7.html
<template>
  <div class="page">
    <div class="navbar">
      <div class="navbar-bg"></div>
      <div class="navbar-inner">
        <div class="title">Store</div>
      </div>
    </div>
    <div class="page-content">
      ${users.value.length > 0 ? $h`
      <div class="list list-strong list-outline-ios list-dividers-ios inset-md">
        <ul>
          ${users.value.map((user) => $h`
          <li class="item-content">
            <div class="item-inner">
              <div class="item-title">${user}</div>
            </div>
          </li>
          `)}
        </ul>
      </div>
      ` : $h`
      <div class="block block-strong block-outline-ios inset-md">
        <a href="#" class="button button-fill button-round button-preloader ${loading.value ? 'button-loading' : ''}"
          @click=${loadUsers}>
          <span class="preloader"></span>
          <span>Load Users</span>
        </a>
      </div>
      `}
    </div>
  </div>
</template>
<script>
  export default (props, { $store }) => {
    const loading = $store.getters.loading;
    const users = $store.getters.users;

    const loadUsers = () => {
      $store.dispatch('getUsers');
    };

    return $render;
  };
</script>