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/lite';

// 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;

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 component:

<!-- pass store to the App's "store" prop -->
<App store={store}>
  <View main>
    <!-- ... -->
  </View>
</App>
<script>
  import { App, View } from 'framework7-svelte';
  // import our store
  import store from 'path/to/store.js';
</script>

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 { f7 } from 'framework7-svelte';

console.log(f7.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 Svelte Components

There is a special useStore helper for use in Svelte components to keep store reactive (auto update components when state/getters values changed).

useStore(getterName, callback)- returns directly getter value and subscribes to the state updates

  • getterName - string - name of the getters handler
  • callback - function - callback function that will be fired with the new getter value when dependant state has been changed.

Method returns getter handler value

If we need to get getter value from another store instance then we also need to pass the store:

useStore(store, getterName, callback)- returns directly getter value and subscribes to the state updates

  • store - store instance - store instance to look getters from. If not specified then default store passed to the <App> component will be used.
  • getterName - string - name of the getters handler
  • callback - function - callback function that will be fired with the new getter value when dependant state has been changed.

Method returns getter handler value

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 Svelte component:

<Page>
  <List>
    {#each users as user}
      <ListItem title={user.name} />
    {/each}
  </List>
</Page>
<script>
  import { onMount } from 'svelte';
  // import special useStore helper/hook
  import { useStore, Page, List, ListItem } from 'framework7-svelte';
  // import store
  import store from 'path/to/store.js'

  // retrieve "users" getter handler value. Initially empty array
  let users = useStore('users', (value) => users = value);

  onMount(() => {
    // load users when component mounted
    store.dispatch('getUsers');
  });

</script>

Because we used Framework7 useStore helper/hook, the component will be auto updated when users loaded.

Examples

import { createStore } from 'framework7/lite';

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;
<Page>
  <Navbar title="Store" />
  {#if users.length}
    <List>
      {#each users as user}
        <ListItem title={user} />
      {/each}
    </List>
  {:else}
    <Block strong>
      <Button fill preloader loading={loading} onClick={loadUsers}>
        Load Users
      </Button>
    </Block>
  {/if}
</Page>
<script>
import { useStore, Page, Navbar, Block, List, ListItem, Button } from 'framework7-svelte';
import store from './store';

let loading = useStore('loading', (value) => loading = value);
let users = useStore('users', (value) => users = value);

const loadUsers = () => {
  store.dispatch('getUsers');
};
</script>