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:
state
- with store statedispatch
- to call actionsgetters
- to access reactive getters handlers
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;
<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">
<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">
<a
href="#"
class="button button-fill 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>