nuxt icon indicating copy to clipboard operation
nuxt copied to clipboard

Use flatted instead of devalue to be able to revive objects

Open vhf opened this issue 6 years ago • 5 comments

What problem does this feature solve?

It is sometimes useful to save complex data (i.e. not primitive types nor POJOs) in the store.

Store data is passed from server to client in serialized form using devalue. Using flatted instead of devalue, together with some configuration mechanism, would allow reviving objects that would then be usable client-side.

For instance developers would add revivers to the store and these would be called client-side to revive serialized values.

What does the proposed changes look like?

I created an example repo which consists of two npx create-nuxt-app: https://github.com/vhf/nuxt-flatted-feature/commits/master . It's a dumb example because I wanted to keep it minimal, in a real-world use case I'm storing things like RDF dataset objects ( https://github.com/rdf-ext/rdf-dataset-indexed ) (initialized during serverIniti based on server data/request data).

In this example I'm storing an object based on:

export default class Bar {
  constructor (id = Math.random()) {
    this.id = id
  }
  transform () {
    return this.id * 2
  }
}

and the goal is to do someBarInstance.transform() in the frontend. The two apps have the same index.vue, the basic one shows an error because .transform is not a function on the devalued object, the other one uses flatted to serialize and revive the object, properly displaying .transform()'s result.

This feature request is available on Nuxt community (#c8733)

vhf avatar Feb 26 '19 10:02 vhf

I was about to open a bug report, because the current behaviour with devalue is not obvious and can lead to serious issues in a strictly typed environment. A major problem tackled here is using data models that are then mapped as props to a component. If I wanted to check these props with the instanceof operator, it may work at one point but fail silently deeper down.

It is unexpected that what is passed in as a data model instance then comes out as an object (essentially only a dictionary mimicking the model) within the component.

As far as I've understood this, Nuxt has forked devalue. The original project does not use toJSON but the fork discussed here already does, though it only works one way. A proper integration with TypeScript includes the ability to use instanceof and preserve types. Either devalue is extended even further to allow for a fromJSON(…) or @vhf has already provided the right direction.

augustsaintfreytag avatar May 28 '19 20:05 augustsaintfreytag

Agreed that it would be really nice to have some ability to reconstitute an object. At a bare minimum having some ability to declare some kind of fromJSON would be really helpful.

justin-schroeder avatar Jun 25 '19 18:06 justin-schroeder

something to consider. flatted seems 2.5x slower than devalue. On 120kb i got this results devalue x 1,655 ops/sec ±0.48% (94 runs sampled) flatted x 635 ops/sec ±1.45% (91 runs sampled)

aldarund avatar Sep 28 '19 14:09 aldarund

+1 Please add some way to hydrate class instances with methods after data injection from the server

lucianholt97 avatar Nov 01 '20 21:11 lucianholt97

I've been working on this problem over the past couple of days. I have it working for my project but want to see what you guys think of my solution.

  1. ​I had to make an additional fix in nuxt-contrib/devalue, I have a PR for this https://github.com/nuxt-contrib/devalue/pull/14
  2. then I changed my model base class to implement toJSON. In my case, I add additional data such as "@className" to let me know what class instance I need to hydrate
  3. finally, in my mixin I have I have the following code.

created()
{
  const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.fetchKey
  if (isSsrHydration(this))
  {
      const nuxtState = window.__NUXT__

      // Hydrate component
      this._fetchKey = this.$vnode.elm.dataset.fetchKey
      const data = nuxtState.fetch[this._fetchKey]

      // If fetch error
      if (data && data._error)
      {
          this.$fetchState.error = data._error
          return
      }

      // Merge data
      for (const key in data)
      {
          let value = data[key];

          if (Array.isArray(value))
          {
              for (let i = 0; i < value.length; i++)
              {
                  const valueElement = value[i];
                  //if it's not an array of models then skip it.
                  if (typeof valueElement !== 'object' || !valueElement["@class"])
                  {
                      break;
                  }

                  let model = this.$getModel(valueElement["@class"], valueElement._id);
                  model.fromJSON(valueElement);
                  value[i] = model;
              }
          }
          else if (typeof value === 'object' && value["@class"])
          {
              let model = this.$getModel(value["@class"], value._id);
              model.fromJSON(value);

              data[key] = model;
          }
      }
  }

CleverLili avatar Mar 18 '21 12:03 CleverLili

Let's track in https://github.com/nuxt/nuxt/issues/12831.

danielroe avatar Feb 15 '23 00:02 danielroe