Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
@Coriou

This comment has been minimized.

Copy link

@Coriou Coriou commented Sep 30, 2018

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 :

// autoStore.js 

import { toJS, autorun, set } 
import store from "store"

export default function(_this) {
	let firstRun = true

	// will run on change
	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) {
				set(_this, existingStore)
			}
		}

		// from then on serialize and save to localStorage
		store.set("store", toJS(_this))
	})

	firstRun = false
}
@andrienko

This comment has been minimized.

Copy link

@andrienko andrienko commented Jan 27, 2019

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)

@lishine

This comment has been minimized.

Copy link

@lishine lishine commented Mar 16, 2020

@andrienko
Interesting why is that slow as you say. Localstorage speed supposed to be 1000/msec

@enif-lee

This comment has been minimized.

Copy link

@enif-lee enif-lee commented Jan 3, 2021

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')
	}
}
@deadcoder0904

This comment has been minimized.

Copy link

@deadcoder0904 deadcoder0904 commented Feb 8, 2021

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

@indapublic

This comment has been minimized.

Copy link

@indapublic indapublic commented Apr 29, 2021

thanks @enif-lee

@deadcoder0904

This comment has been minimized.

Copy link

@deadcoder0904 deadcoder0904 commented Apr 30, 2021

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

This comment has been minimized.

Copy link

@dizys 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', ['counter']); // Only store property `counter`
  }
}
@deadcoder0904

This comment has been minimized.

Copy link

@deadcoder0904 deadcoder0904 commented Jul 13, 2021

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

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