I’ve been using Aurelia Store to persist my local state into local storage.
I’ve noticed that if any changes are made to my initialState declaration (e.g new properties, or changes to default values), they are discarded when the rehydrateFromLocalStorage action is dispatched on refresh.
My current approach is :
export class App() {
constructor(private store: Store<State>) {
// Register the middleware
store.registerMiddleware(localStorageMiddleware, MiddlewarePlacement.After, {key: 'storage-key' });
// Register the rehydration action
store.registerAction('Rehydrate', rehydrateFromLocalStorage);
// ... Other state actions declared here
dispatchify('Rehydrate')('storage-key');
}
}
If I were to subscribe to the state, add a new property to the initial state, and refresh - I can see that the new initial state is loaded correctly until the rehydrate action is dispatched.
At this point the state subscriber returns exactly what was stored in local storage before the changes, excluding any new/updated properties or default values.
Do I need to change the way I’m configuring the plugin, or is there a standard practice for managing changes like this?
That would require the store to know how to diff/merge the two states which is a very generic task. In this case I’d recommend to build your own rehydrate action and do the sanitization/merging right in there
so something along the lines of
import {rehydrateFromLocalStorage} from "aurelia-store";
export function customRehydrate(state: State, key?: string) {
const newState = rehydrateFromLocalStorage(state, key);
// modify newState according to your needs but keep the shallow cloning in mind
return newState;
}
I’ve ended up using a combination of both of these suggestions :
export function customRehydrateAction(state: State, key?: string) {
return syncState(
{... state},
rehydrateFromLocalStorage(state, key)
) as State;
}
// Recursively syncs two given state objects
function syncState(initialState: {}, fromStorage: {}) {
Object.keys(initialState).forEach(key => {
// If we don't have a value in our local storage, leave the initial value as is.
if (!(key in fromStorage)) {
return;
}
// If both keys are Objects, excluding arrays, recursively sync the entries of each.
if (
isObject(initialState[key]) &&
isObject(fromStorage[key]) &&
!Array.isArray(initialState[key])
) {
initialState[key] = syncState(initialState[key], fromStorage[key]);
return;
}
// If we get here, just assign our stored value to our state.
initialState[key] = fromStorage[key];
});
return initialState;
}
This appears to be working nicely, and has the added benefits of clearing out keys and values which are no longer present in the initialState.