I'm trying to save state using localForage with every property change or at least at browser/tab close.
so, have you had any problems with that?
I've not used localForage myself, but it looks like something worth investigating. Would making a library for integration with Hyperapp add any value?
@stsdc
HOC that listens after action calls and then dumps to LF?
@okwolf loves them HOCs 馃
I just use localStorage, and certain actions just localStorage.state = JSON.stringify(newState) before returning the new state. Not everything needs to be saved to localStorage 馃槃
Example here: https://github.com/selfup/hmrcmd/blob/master/src/actions/index.js#L144
Then on app mount:
const {
syncFromLocalStorage,
} = app(...);
if (localStorage.state) {
syncFromLocalStorage(JSON.parse(localStorage.state));
}
Examples here:
That action just returns the parsed JSON and then HA syncs everything 馃帀
I made a library called lspi for localStorage stuff, but if you are just syncing on load and after each action you won't need it.
Found hyperapp-persist and hyperapp-persist-state. Thought to merge them and mix with localForage. 鈿楋笍
@stsdc Sure try them out but there are a lot of little repos like those which were abandoned at one point or another as Hyperapp's api changed over and over (pre 1.0, people gave up trying to keep up at various points). Just something to be aware of.
I'd probably roll my own just to be safe. A function to use like this
import {h, app as _app} from 'hyperapp'
import withPersistentState from 'my-state-persister'
const app = withPersistentState(_app)
app(state, actions, view)
withPersistentState would wrap the call to _app by providing the persisted state if it exists, or the given state otherwise, also wrap all provided actions to persist the state each time they're called.
However, I'm not really sure it's a good idea to always persist all the state of the app. More likely what you really want is to persist some of the state some of the time.
@zaceno I'm aware that persisting whole the state is no good. Here is my current state it will probably be much bigger. Would You recommend to deconstruct the state to smaller substates and save them as keys in the Storage? If so, then I have to save data to the Storage and to the state both.
Is the concern with persisting the whole state the size of the serialized data or the performance hit of persisting on every state update?
Regarding what state you need to persist, I would say anything the user would be mad if you used the default state value instead of the current. If you're syncing data to a remote API though, you may only need to persist your login auth token and routing info.
I've been persisting bits & pieces of my app state, like @okwolf is suggesting. Auth info and search filtering settings, for example.
To achieve this, I created a helper function which returns functions to help me save to localStorage and restore from localStorage. This does require manually wiring things up, but it's explicit and easy to understand.
export function storage(LOCALSTORAGE_KEY) {
return {
persist(data) {
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(data))
return data
},
restore(defaultData) {
// Load the previous state from local storage
let previous = { ...defaultData }
let found = false
try {
const data = localStorage.getItem(LOCALSTORAGE_KEY)
if (data) {
previous = JSON.parse(data)
found = true
}
} catch (e) {
void 0
}
return {
previous,
found,
}
},
}
}
import { storage } from './helpers/storage'
const LOCALSTORAGE_KEY = 'auth'
const { persist, restore } = storage(LOCALSTORAGE_KEY)
const defaultAuth = { id: null, scope: null, verified: null }
// Load the previous state from local storage on app startup
let { previous } = restore(defaultAuth)
export const AuthModel = {
state: {
...previous,
},
actions: {
set: data => persist(data),
clear: () => persist({ ...defaultAuth }),
},
}
Looks nice, but what to do when I have to save nested object?
let state = {
buildings: {
schools: { ... },
libraries: { ... },
malls: { ... }
},
vehicles: { ... },
....
};
If I'm updating only schools, I want to update whole buildings also.
In HA 1.x I have certain slices that I set to local storage on certain events, like a save button. I have the save-to-local-storage logic in an action somewhere.
I have a top-level action called update that looks like this.
actions: {
update: data => data
}
When my app loads I get the local storage and apply the state to that entire slice using interop.
const LocalStorage = main => {
const foo = window.localStorage.getItem('site/foo')
const bar = window.localStorage.getItem('site/bar')
const baz = window.localStorage.getItem('site/baz')
// state object
const state = {}
// apply existing data
if (foo) state.Foo = JSON.parse(foo)
if (bar) state.Bar = JSON.parse(bar)
if (baz) {
state.Baz = {
foo: expireLeads(JSON.parse(baz))
}
}
// populate state
main.update(state)
}
I realized this isn't really much different than SkaterDad's approach, however I don't feel like a helper is necessary in this case. I just prefer to just use the local storage api directly in my code.
Persisting the entire state is not usually desirable, but what I would use it for is developing with an auto-reloading server where persisting everything achieves something similar to hot-reloading. Probably discussion somewhere about this previously.
In any case I agree with @okwolf, that it's a separation of how much is saved and how frequent its saved. I like @SkaterDad's simple and flexible approach to this. :smile:
Edit: On the point of localStorage versuses IndexedDB, I would guess that localStorage is preferrable for the efficiency of saving. Has anyone experienced problems or tried to make IndexedDB persistence yet?
defining state properties with descriptors (like enumerable:false)
omits them when saving to localstorage (because of stringify),
so it is possible to save only required state properties
@sergey-shpak that is neat trick! :pray:
In V2, I'll explore persisting the state using perhaps an effect or a subscription.
For example, using localForage and a subscription:
// persist.js
import localForage from "localforage"
const handleError = console.log
const persistFx = state => localForage.setItem("state", state, handleError)
export const persist = state => [persistFx, state]
// main.js
import { h, app } from "hyperapp"
import { persist } from "./persist"
app({
...,
subscriptions: state => [persist(state), ...]
})
Most helpful comment
I've been persisting bits & pieces of my app state, like @okwolf is suggesting. Auth info and search filtering settings, for example.
To achieve this, I created a helper function which returns functions to help me save to localStorage and restore from localStorage. This does require manually wiring things up, but it's explicit and easy to understand.