Skip to content

Instantly share code, notes, and snippets.

@du5rte
Last active March 8, 2023 12:10
Show Gist options
  • Save du5rte/dbd18a1a6dc72d866737a5e95ca1e663 to your computer and use it in GitHub Desktop.
Save du5rte/dbd18a1a6dc72d866737a5e95ca1e663 to your computer and use it in GitHub Desktop.
Auto saving to localStorage with MobX
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
@deadcoder0904
Copy link

@enif-lee thank you, that works like a charm. so simple, it feels like cheating 😝

@indapublic
Copy link

thanks @enif-lee

@deadcoder0904
Copy link

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 🎉

@dizys
Copy link

dizys commented Jul 13, 2021

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`
  }
}

@deadcoder0904
Copy link

@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 🎉

@hartum
Copy link

hartum commented Oct 29, 2021

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.

@deadcoder0904
Copy link

@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 :)

@hartum
Copy link

hartum commented Nov 2, 2021

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.

@hartum
Copy link

hartum commented Nov 8, 2021

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment