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:
import React from 'react';
import { App, View } from 'framework7-react';
// import our store
import store from 'path/to/store.js';
export const App = () => {
// ...
return (
{/* pass store to the App's "store" prop */ }
<App store={store}>
<View main>
{/* ... */}
</View>
</App>
)
}
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-react';
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 React Components
There is a special useStore
helper for use in React components to keep store reactive (auto update components when state/getters values changed).
useStore(getterName)- returns directly getter value and subscribes to the state updates
- getterName - string - name of the getters handler
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)- 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
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 React component:
import React, { useEffect } from 'react';
// import special useStore helper/hook
import { useStore, Page, List, ListItem } from 'framework7-react';
// import store
import store from 'path/to/store.js'
export const UsersPage = () => {
// retrieve "users" getter handler value. Initially empty array
const users = useStore('users');
useEffect(() => {
// load users when component mounted
store.dispatch('getUsers');
}, []);
return (
<Page>
<List>
{users.map((user, index) => (
<ListItem title={user.name} key={index} />
))}
</List>
</Page>
)
}
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;
import React from 'react';
import { useStore, Page, Navbar, Block, List, ListItem, Button } from 'framework7-react';
import store from './store';
export default () => {
const loading = useStore('loading');
const users = useStore('users');
const loadUsers = () => {
store.dispatch('getUsers');
};
return (
<Page>
<Navbar title="Store" />
{users.length && (
<List>
{users.map((user) => (
<ListItem title={user} key={user} />
))}
</List>
)}
{!users.length && (
<Block strong>
<Button fill preloader loading={loading} onClick={loadUsers}>
Load Users
</Button>
</Block>
)}
</Page>
);
};