funkensturm / ember-local-storage

The addon provides a storageFor computed property that returns a proxy and persists the changes to localStorage or sessionStorage. It ships with an ember-data adapter.
https://www.funkensturm.com/ember-local-storage/
MIT License
218 stars 76 forks source link

Local storage issues with native classes (Ember octane) #330

Closed rnuyts closed 2 years ago

rnuyts commented 2 years ago

We use ember-source with version ~3.24.0. I tried several solutions to make working the local storage in a Glimmer component (native class) without success.

Here are my investigations :

1) First solution

import Component from '@glimmer/component';
import { storageFor } from 'ember-local-storage';
import EmberObject, { get, set } from '@ember/object';

export default class CLocalStorageTest extends Component {

  messageStorage = storageFor('message', 'storageScope');

  constructor() {
    super(...arguments);

    console.log('VALUE FROM LOCAL STORAGE : ', get(this, 'messageStorage.value'));
    set(this, 'messageStorage.value', 'Hello world!');
    console.log('NEW VALUE SAVED IN LOCAL STORAGE : ', get(this, 'messageStorage.value'));
  }

  get storageScope() {
    return EmberObject.create({
      modelName: 'user',
      id: '999'
    });
  }

}

In this case, the value is not persisted in the local storage. So, when I re-render the component, the value displayed in console.log is always undefined.

2) Second solution : without storageScope

import Component from '@glimmer/component';
import { storageFor } from 'ember-local-storage';
import { get, set } from '@ember/object';

export default class CLocalStorageTest extends Component {

  messageStorage = storageFor('message');

  constructor() {
    super(...arguments);

    console.log('VALUE FROM LOCAL STORAGE : ', get(this, 'messageStorage.value'));
    set(this, 'messageStorage.value', 'Hello world!');
    console.log('NEW VALUE SAVED IN LOCAL STORAGE : ', get(this, 'messageStorage.value'));
  }

}

I got the same result than in solution 1.

3) Third solution : Use the decorator like explained here : https://github.com/funkensturm/ember-local-storage/issues/325#issuecomment-830297250

import Component from '@glimmer/component';
import { storageFor } from 'ember-local-storage';

export default class CLocalStorageTest extends Component {

  @storageFor('message') messageStorage;

  constructor() {
    super(...arguments);

    console.log('VALUE FROM LOCAL STORAGE : ', this.messageStorage.get('value'));
    this.messageStorage.set('value', 'Hello worlds !');
    console.log('NEW VALUE SAVED IN LOCAL STORAGE : ', this.messageStorage.get('value'));
  }

}

I got an error :

unauthorized-errors-handler.js:24 TypeError: Unknown StorageFactory: storage:message at createStorage (storage.js:108) at CLocalStorageTest. (storage.js:51) at index.js:1918 at untrack (validator.js:842) at ComputedProperty.get (index.js:1917) at CLocalStorageTest.getter [as messageStorage] (index.js:1007) at new CLocalStorageTest (component.js:25) at EmberGlimmerComponentManager.createComponent (base-component-manager.js:39) at EmberGlimmerComponentManager.createComponent (ember-component-manager.js:52) at index.js:5377

So, what is wrong with what I tried ? What is the right way to use ember-local-storage with native classes (Octane / Glimmer) ?

Thank you in advance for your help.

fsmanuel commented 2 years ago

@rnuyts the option 3 using a decorator should work. Did you define/create the storage object?

rnuyts commented 2 years ago

@fsmanuel, Ok I just forgot to add the message.js file in the storages folder.

Now, it's done and it works. But, as soon as I define a model in the @storageFor decorator :

import Component from '@glimmer/component';
import { storageFor } from 'ember-local-storage';
import EmberObject from '@ember/object';

export default class CLocalStorageTest extends Component {

  @storageFor('message', 'storageScope') messageStorage;

  constructor() {
    super(...arguments);

    console.log('VALUE FROM LOCAL STORAGE : ', this.messageStorage.get('value'));
    this.messageStorage.set('value', 'Hello world !');
    console.log('NEW VALUE SAVED IN LOCAL STORAGE : ', this.messageStorage.get('value'));
  }

  get storageScope() {
    return EmberObject.create({
      modelName: 'user',
      id: '999'
    });
  }

}

I get this error : unauthorized-errors-handler.js:24 TypeError: this.get is not a function at CLocalStorageTest. (storage.js:60) at index.js:1918 at untrack (validator.js:842) at ComputedProperty.get (index.js:1917) at CLocalStorageTest.getter [as messageStorage] (index.js:1007) at new CLocalStorageTest (component.js:25) at EmberGlimmerComponentManager.createComponent (base-component-manager.js:39) at EmberGlimmerComponentManager.createComponent (ember-component-manager.js:52) at index.js:5377 at deprecateMutationsInTrackingTransaction (validator.js:188)

Setting a model (in storageFor) based on a computed works perfectly in our classes that haven't been migrated to Octane. But, it seems to be broken when we use the decorator solution and providing the model from a getter. Do you have a solution for this problem ?

rnuyts commented 2 years ago

Apparently, as soon as you use the decorator and you pass the model attribute, it doesn't work. The handling of the model attribute is based on the fact that the calling class is an "Ember" class with "this.get()" method.

rnuyts commented 2 years ago

The only fact to call the storage once it has been defined with the decorator and the model attribute throws the exception :

import Component from '@glimmer/component';
import { storageFor } from 'ember-local-storage';
import EmberObject from '@ember/object';

export default class CLocalStorageTest extends Component {

  @storageFor('message', 'storageScope') messageStorage;

  constructor() {
    super(...arguments);

    this.messageStorage;   // error is thrown from here
    // console.log('VALUE FROM LOCAL STORAGE : ', this.messageStorage.get('value'));
    // this.messageStorage.set('value', 'Hello world !');
    // console.log('NEW VALUE SAVED IN LOCAL STORAGE : ', this.messageStorage.get('value'));
  }

  get storageScope() {
    return EmberObject.create({
      modelName: 'user',
      id: '999'
    });
  }

}

=>

unauthorized-errors-handler.js:24 TypeError: this.get is not a function at CLocalStorageTest. (storage.js:60) at index.js:1918 at untrack (validator.js:842) at ComputedProperty.get (index.js:1917) at CLocalStorageTest.getter [as messageStorage] (index.js:1007) at new CLocalStorageTest (component.js:25) at EmberGlimmerComponentManager.createComponent (base-component-manager.js:39) at EmberGlimmerComponentManager.createComponent (ember-component-manager.js:52) at index.js:5377 at deprecateMutationsInTrackingTransaction (validator.js:188)

fsmanuel commented 2 years ago

Interesting! I think it has do with how the addon uses the context to get the model property. I’ll investigate how it can work for classic ember objects and native classes. Thanks a lot for reporting!

rnuyts commented 2 years ago

@fsmanuel, I suppose replacing https://github.com/funkensturm/ember-local-storage/blob/dcc87a697a75f6b8da532f48e563fa178b17283d/addon/helpers/storage.js#L60 by

const model = get(this, modelName);

should do the trick.

fsmanuel commented 2 years ago

@rnuyts I release version 2.0 that includes the fix. Please let me know if you find more problems with native classes.

rnuyts commented 2 years ago

Thank you @fsmanuel ! I will test it next week and will let you know if it’s OK from our side 👌

rnuyts commented 2 years ago

@fsmanuel, I confirm that it's working in our app in version 2.0.0. Thank you for the fix !

fsmanuel commented 2 years ago

@rnuyts great that it works!