-
-
Save du5rte/dbd18a1a6dc72d866737a5e95ca1e663 to your computer and use it in GitHub Desktop.
import mobx from "mobx" | |
import store from "store" | |
export default function(_this) { | |
let firstRun = true | |
// will run on change | |
mobx.autorun(() => { | |
// on load check if there's an existing store on localStorage and extend the store | |
if (firstRun) { | |
const existingStore = store.get("store") | |
if (existingStore) { | |
mobx.extendObservable(_this, existingStore) | |
} | |
} | |
// from then on serialize and save to localStorage | |
store.set("store", mobx.toJS(_this)) | |
}) | |
firstRun = false | |
} |
import mobx, { computed, observable } from "mobx" | |
import autoSave from "./autoSave" | |
const initialTodoStore = { | |
todos: ["buy milk", "buy eggs"] | |
} | |
class TodoStore { | |
constructor() { | |
// set initial mock up examples | |
this.todos = initialTodoStore // or [] | |
// in the future it will over run it and save to store | |
autoSave(this) | |
} | |
@observable todos | |
@observable filter = "" | |
@computed get filteredTodos() { | |
const filter = new RegExp(this.filter, "i") | |
return this.todos.filter(todo => !this.filter || filter.test(todo)) | |
} | |
} | |
const todoStore = window.todoStore = new TodoStore | |
export default todoStore |
Once I have tried similar approach in my app - and it worked prety slow for stores containing couple dozens pretty simple values - due to the fact that it had to serialize values on each save, so, for example, on every typed letter in controlled component.
I ended up writing an "universal" function to autosave selected primitive values from a store using reaction
; and writing reactions for each specific case when I needed.
Also, the store
library turned up to be unpredictable and unnecessarily complex, especially when you are trying to use it for session storage, so I ended up writing my own one (pretty simple one, actually)
@andrienko
Interesting why is that slow as you say. Localstorage speed supposed to be 1000/msec
after mobx 6, the property cannot be updated on autorun directly. autorun is not in action. all observable property must be updated on action
in mobx6.
so... here is simple sample for persist to localstorage automatically.
import { autorun, set, toJS } from 'mobx'
export function autoSave(_this: any, name: string) {
const storedJson = localStorage.getItem(name)
if (storedJson) {
set(_this, JSON.parse(storedJson))
}
autorun(() => {
const value = toJS(_this)
localStorage.setItem(name, JSON.stringify(value))
})
}
and
export class AuthStore {
public accessToken: string
constructor() {
makeAutoObservable(this)
this.accessToken = '' // initial value setup must be placed here before update by autoSave
autoSave(this, 'authStore')
}
}
@enif-lee thank you, that works like a charm. so simple, it feels like cheating 😝
thanks @enif-lee
I have found an even more simpler solution using toJSON()
:
import { autorun } from 'mobx'
const name = 'Store'
export class Store {
constructor() {
const storedJson = localStorage.getItem(name)
if (storedJson) Object.assign(this, JSON.parse(storedJson))
autorun(() => {
localStorage.setItem(name, JSON.stringify(this))
})
}
toJSON() {
const { id, background } = this
return {
id,
background
}
}
reset() {
localStorage.removeItem(name)
}
}
Works like a charm with MobX 6 🎉
My helper function as a universal solution:
import {autorun, toJS} from 'mobx';
export function makeLocalStorage<T extends object, K extends keyof T>(
obj: T,
prefix: string,
keys: K[],
): void {
for (const key of keys) {
const localKey = `${prefix}_${key}`;
const valueStr = localStorage.getItem(localKey);
if (!valueStr) {
continue;
}
const value = JSON.parse(valueStr);
obj[key] = value;
}
autorun(() => {
for (const key of keys) {
const localKey = `${prefix}_${key}`;
localStorage.setItem(localKey, JSON.stringify(toJS(obj[key])));
}
});
}
Usage:
import {makeObservable, observable} from 'mobx';
class CounterStore {
count = 0;
countThatDoesNotNeedToBeLocallyStored = 0;
constructor() {
makeObservable(this, {
count: observable,
countThatDoesNotNeedToBeLocallyStored: observable
});
makeLocalStorage(this, 'counterStore', ['count']); // Only store property `count`
}
}
@dizys nice one. Currently, I use mobx-persist-store as it's a library plus battle-tested with edge-cases. Works with TypeScript as well 🎉
Hi there
I'm trying to read read/write in store but from differents browser tabs:
URL for tab1: /mydomain
URL for tab2: /mydomain/test
I'm trying to figure out how to set reaction to listen changes in localstorage. The flow will be like this:
tab1 set var visible in store to true, then tab2 listen the change (from localstorage I suppose) and show something into tab2.
Is it possible to do that? Any idea how to do it?
thanks in advance.
@hartum websockets maybe? idk why you wanna persist such data in localstorage but i guess hmr in react/next works like that only. they use websockets to listen if ui in one browser changed & update it everywhere i guess :)
Using websockets seems to me a bit drastic solution. The reason is two browser windows/tabs of the same SPA, because the client wants to open several instances of components and be able to use it on several monitors.
In case anyone is interested what I did to solve my problem was to use the 'mobx-persist-store' library to write my store data to the localStorage.
Then (inside my store) I added a store event listener and update it every time I detect a change.
in mobxStore.ts
import { makePersistable, hydrateStore } from 'mobx-persist-store';
class Store {
// --- STORE STATE VARS ---
dashboard = {
isVisible: true,
};
constructor() {
makePersistable(this, { name: 'mobxStore',
properties: ['dashboard'],
storage: window.localStorage }).finally(() => {});
}
// --- SHOW/HIDE DASHBOARD ---
setDashboard(visible: boolean) {
this.dashboard.isVisible = visible;
}
}
const myStore = new Store();
window.addEventListener('storage', (e) => {
if (e.key === 'mobxStore') {
myStore.hydrateStore().catch(() => {});
}
});
export default myStore;
in a different window (but same domain http://localhost:3000/test )the component:
import React from 'react';
import { observer } from 'mobx-react';
import store from 'store/mobxStore';
const FakeComponent = () => {
const visible = store.dashboard.isVisible;
return (
<div>
{store.dashboard.isVisible ? <span>Dashboard visible</span> : <span>Dashboard Invisible</span>}
<br />
<div onClick={() => store.setDashboard(!visible)} >"Switch dashboard visibility"</div>
</div>
);
};
export default observer(FakeComponent);
Hey, thanks for this. Still ranking high in Google, so I thought I'd share my version that work with Mobx 5. What changes is I replaced "extendObservable" with "set" and changed the way MobX methods are imported :