question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Infinite render loop when instantiating 2 new models

See original GitHub issue

First of all, thanks a lot for this excellent framework which finally brings some strong opinions as of how Vuex should be structured and configured šŸ’Ŗ

Steps to reproduce

I am reproducing the CRUD form example from the documentation using FeathersVuexFormWrapper.

Things work fine when I am using a single instance of a form wrapper, which contains a new model initializer. Below is my code snippet reproducing the logic from the documentation.

Now, I want to mount another component that also uses FeathersVuexFormWrapper, and also contains a new model initializer. For my use case, I actually have a companyFormWrapper and a personFromWrapper. But the problem also arises when Iā€™m simply mounting any of these components twice on the same page - basically, anytime there is more than one new XXX() instantiation instruction on the same page.

Could you kindly let me know what Iā€™m doing wrong here?

EDIT: I realize that this is also happening when one new instruction exists in a component and another one exists in the servicePlugin. For example I have this when loading relations with setupInstance, where I instantiate a new model for each linked object. So I guess there is something deep down that I really havenā€™t understood about the workings of the framework?

Expected behavior

Each form wrapper should correctly instantiate its new model if no existing model is provided for edition.

Actual behavior

Anytime there is more than one new XXX() instantiation instruction on the same page, an infinite render loop occurs. I get the following message in the console:

vue.runtime.esm.js?2b0e:619 [Vue warn]: You may have an infinite update loop in watcher with expression "item"

found in

---> <PersonFormWrapper>
       <List> at src/views/dataroom/entities/List.vue
         <Stakeholders> at src/views/dataroom/Stakeholders.vue
           <DefaultLayout> at src/layouts/Default.vue
             <App> at src/App.vue
               <Root>

I have put a watcher on the item property and it enters an infinite refresh loop where temp id is incremented forever:

assigning temporary id to item PersonĀ {documents: Array(0), addresses: Array(0)}
**logger.js?b054:87 mutation people/addItem @ 22:29:37.904**
PersonFormWrapper.vue?0ad9:44 ITEM {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042ede","__isTemp":true} {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042edc","__isTemp":true}
PersonFormWrapper.vue?0ad9:44 ITEM {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042ede","__isTemp":true} {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042ede","__isTemp":true}
**logger.js?b054:87 mutation people/createCopy @ 22:29:37.914**
utils.js?225b:158 assigning temporary id to item PersonĀ {documents: Array(0), addresses: Array(0)}
**logger.js?b054:87 mutation people/addItem @ 22:29:37.922**
PersonFormWrapper.vue?0ad9:44 ITEM {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042edf","__isTemp":true} {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042edd","__isTemp":true}
utils.js?225b:158 assigning temporary id to item PersonĀ {documents: Array(0), addresses: Array(0)}
**logger.js?b054:87 mutation people/addItem @ 22:29:37.930**
PersonFormWrapper.vue?0ad9:44 ITEM {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042ee0","__isTemp":true} {"documents":[],"addresses":[],"__id":"5ec987b14cf79c9a15042ede","__isTemp":true}

Configuration

ā€œ@feathersjs/feathersā€: ā€œ^4.5.3ā€, ā€œ@feathersjs/rest-clientā€: ā€œ^4.5.4ā€, ā€œfeathers-hooks-commonā€: ā€œ^5.0.3ā€, ā€œfeathers-vuexā€: ā€œ^3.10.4ā€,

The client is configured as follows:

const feathersClient = feathers()
  .configure(restClient.axios(axios))
  .hooks({
    before: {
      all: [
        iff(
          context => ['create', 'update', 'patch'].includes(context.method),
          discard('__id', '__isTemp')
        )
      ]
    }
  })

export default feathersClient

const { makeServicePlugin, makeAuthPlugin, BaseModel, models, FeathersVuex } = feathersVuex(
  feathersClient,
  {
    idField: 'id',
    nameStyle: 'path',
    preferUpdate: true,
    replaceItems: true,
    debug: ['development', 'staging'].includes(process.env.NODE_ENV)
  }
)

export { makeAuthPlugin, makeServicePlugin, BaseModel, models, FeathersVuex }

My service is plain vanilla from the documentation:

import feathersClient, { makeServicePlugin, BaseModel } from '../../../feathers-client'

class Person extends BaseModel {
  static modelName = 'Person'
  static instanceDefaults () {
    return {
      documents: [],
      addresses: []
    }
  }
}

const servicePath = 'people'

const servicePlugin = makeServicePlugin({
  Model: Person,
  service: feathersClient.service('people'),
  servicePath
})

export default servicePlugin

and finally here is the actual form component:

<template>
  <FeathersVuexFormWrapper
    :item="item"
    watch
  >
    <template v-slot="{ clone, save, reset }">
      <person-editor
        :modal-id="modalId"
        :item="clone"
        @close="handleClose"
        @save="save().then(handleClose)"
        @reset="reset"
      />
    </template>
  </FeathersVuexFormWrapper>
</template>

<script>
import PersonEditor from './PersonEditor'
import { models, FeathersVuexFormWrapper } from 'feathers-vuex'

export default {
  components: {
    PersonEditor,
    FeathersVuexFormWrapper
  },
  data () {
    return {
      modalId: 'personFormModal',
      itemId: null
    }
  },
  computed: {
    item () {
      const { Person } = models.api
      // As soon as 2 models are instantiated with `new XXX()` on the same page, an infinite loop occurs. If the `new Person()` instruction is replaced by `{}`, the form crashes on new items but the infinite loop disappears.
      return this.itemId ? Person.getFromStore(this.itemId) : new Person()
    }
  },
  watch: {
    item: function (newVal, oldVal) {
      // Watching this shows an infinite loop of temp id increments
      console.log('ITEM', JSON.stringify(newVal), JSON.stringify(oldVal))
    }
  },
  methods: {
    // Note that all these methods are called via tight coupling directly from the parent component.
    handleAdd () {
      this.itemId = null
      this.$bvModal.show(this.modalId)
    },
    handleEdit ({ id }) {
      this.itemId = id
      this.$bvModal.show(this.modalId)
    },
    handleClose () {
      this.itemId = null
      this.$bvModal.hide(this.modalId)
    }
  }
}
</script>

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
hamiltoescommented, Jun 24, 2020

After a superficial read-through, my best guess is that the issue is youā€™re calling new Person() and getFromStore in the same computed. new Person() is going to create a new Person Model and store it the serviceā€™s Vuex state in tempsById. But then getFromStore will cause the computed to fire again because it is reactive to tempsById changes. But itemId is still null so it again calls new Person()ā€¦ thus the infinite loop.

When instantiating a model, you can pass a second parameter to the modelā€™s constructor { commit: false } which will not add the temporary item to the store.

But based on your usage of the model as a placeholder, when your data itemId is null, I would suggest having a data field on your component called placeholder or something that is instantiated once when your component is created and then in your computed, return that placeholder in your ternary instead of calling new Person().

The following is untested and everything not relevant to what Iā€™m discussing is removed for simplicityā€™s sake.

import PersonEditor from './PersonEditor'
import { models, FeathersVuexFormWrapper } from 'feathers-vuex'

const { Person } = models.api;

export default {
  data () {
    return {
      placeholder: new Person({}, { commit: false })  // <-- create placeholder model once during component init and don't add to store
    }
  },
  computed: {
    item () {
      return this.itemId ? Person.getFromStore(this.itemId) : this.placeholder <-- return the placeholder
    }
  },
}
0reactions
thomasvdscommented, Jan 17, 2021

Itā€™s been quite some time and now I bumped into this issue again on another project. Strangely enough @hamiltoes the solution youā€™re suggesting works fine when using { commit: true } (otherwise itā€™s not possible to actually save new instances). Thanks for that!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Cause of infinite re-render loop in a class component?
The error message I'm getting is: "Too many re-renders. React limits the number of renders to prevent an infinite loop." The props being...
Read more >
3 ways to cause an infinite loop in React - Alex Sidorenko
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. Here are 3 potential causes of theĀ ......
Read more >
Object Pooling in Unity | Kodeco, the new raywenderlich.com
The for loop will instantiate the objectToPool GameObject the specified number of times in numberToPool . Then the GameObjects are set to anĀ ......
Read more >
Top 18 Most Common AngularJS Developer Mistakes
Top 18 Most Common Mistakes that AngularJS Developers Make Ā· a) Causing too many digest loops, such as attaching sliders to models Ā·...
Read more >
programming Flashcards
An infinite while loop is produced when the condition being tested is always true. ... new. the keyword required to instantiate a Java...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found