Home > OS >  Property or method "_" is not defined on the instance but referenced during render
Property or method "_" is not defined on the instance but referenced during render

Time:06-01

Im a capable react developer, but have inherited a vue.js project from another developer and have maintained it for several years now, unfortunately I haven't gone through much personal effort to learn vue as i should.

I have a strange error being thrown from using lodash, I believe it doesnt like my _.debounce call

Component:
<script>
import _ from 'lodash'
import CostCodeField from '@/components/workdays/CostCodeField'

// ...
</script>

<template lang='html'>
  <!-- relevant code snippet -->
  <!-- ... -->
  <b-table  :data="workday.charges" :striped="true" :mobile-cards="false" :row->
    <b-table-column label="Cost Code" width="260">
        <b-field expanded="expanded">
          <cost-code-field
            :value="props.row.cost_code.number" :disabled="timecard.is_submitted || locked || isLoading(props)"
            :job="props.row.job"
            @input="set($event, props.index, 'cost_code')"
            @change="_.debounce(submit(props.row, props.index), 100)"
          ></cost-code-field>
          <p >
            <a
              
              @click="triggerCostCode(props.row, props.index)" :disabled="props.row.job.jd_job_number_id === undefined || timecard.is_submitted || isLoading(props)"
            >
              <b-icon icon="magnify"></b-icon>
            </a>
          </p>
        </b-field>
      </b-table-column>
      <!-- ... -->
  </b-table>
</template>
console.error
[Vue warn]: Property or method "_" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.

found in

---> <ChargesTable> at src/components/workdays/ChargesTable.vue
       <BTabItem>
         <BTabs>
           <WorkdayListItem> at src/components/workdays/WorkdayListItem.vue
             <Timecard> at src/components/timecards/Timecard.vue
               <App> at src/App.vue
                 <Root> vue.esm.js:628
    VueJS 3
    change ChargesTable.vue:127
    VueJS 4
    setCode CostCodeField.vue:108
    VueJS 12
    mutations timecards.js:322
    mutations timecards.js:319
    wrappedMutationHandler vuex.esm.js:844
    commitIterator vuex.esm.js:466
    commit vuex.esm.js:465
    _withCommit vuex.esm.js:624
    commit vuex.esm.js:464
    boundCommit vuex.esm.js:409
    submit CostCodeLookup.vue:134
    submit CostCodeLookup.vue:16
    VueJS 33
[Vue warn]: Error in v-on handler: "TypeError: _vm._ is undefined"

found in

---> <CostCodeField> at src/components/workdays/CostCodeField.vue
       <BField>
         <BTableColumn>
           <BTable>
             <ChargesTable> at src/components/workdays/ChargesTable.vue
               <BTabItem>
                 <BTabs>
                   <WorkdayListItem> at src/components/workdays/WorkdayListItem.vue
                     <Timecard> at src/components/timecards/Timecard.vue
                       <App> at src/App.vue
                         <Root> vue.esm.js:628
TypeError: _vm._ is undefined
    change ChargesTable.vue:127
    VueJS 4
    setCode CostCodeField.vue:108
    VueJS 12
    mutations timecards.js:322
    mutations timecards.js:319
    wrappedMutationHandler vuex.esm.js:844
    commitIterator vuex.esm.js:466
    commit vuex.esm.js:465
    _withCommit vuex.esm.js:624
    commit vuex.esm.js:464
    boundCommit vuex.esm.js:409
    submit CostCodeLookup.vue:134
    submit CostCodeLookup.vue:16
    VueJS 33

CodePudding user response:

Any var referenced in template is prefixed with this under the hood. Which means you can't reference global vars in the template, unless they're expressly declared in its context.

Simply put, the template context is an object. If you don't define a particular key and don't assign it a particular value, it's going to be undefined.

Probably the most concise way of exposing globals to the template is using computed:

// ...
  computed: {
    _: () => _
  }
// ...

In Composition API, the simplest way to expose globals is to add them in the object returned by setup:

export default {
  // ... 
  setup() {
    //...
    return {
      _,
      //...
    }
  }
}

CodePudding user response:

Accessing _ in template

Assuming you're using the Options API, importing _ does not automatically make it available to the template (as @tao pointed out in his answer). The template can only access fields exposed via the component options (e.g., data, props, methods, computed, etc.) in addition to a few allow-listed globals (Vue 2 allowed globals, Vue 3 allowed globals).

Using _.debounce

_.debounce's first argument is a function reference:

_.debounce(submit(props.row, props.index), 100) // ❌ calls `submit()`, and passes the result to _.debounce()

To create a debounced submit, pass the submit-reference as an argument to _.debounce like this:

_.debounce(submit, 100)

You technically could invoke the debounced function immediately:

_.debounce(submit, 100)(props.row, props.index)

...but don't do that in the template (see reason below).

Using a debounced event handler for v-on

When the value of the v-on directive (@ for shorthand) is an expression (as in your case), the template compiler automatically wraps the expression in a function, so this:

@change="_.debounce(submit, 100)"

...essentially becomes:

@change="($event) => _.debounce(submit, 100)"

...which would have no effect, since debounce doesn't invoke the wrapped function itself.

You might be tempted to call the function immmediately in:

@change="_.debounce(submit, 100)(props.row, props.index)"

...but that creates a new debounced function on every event, which defeats the debouncing.

Solution

Create a debounced function in the <script> part of the SFC that could then be used as the v-on value:

Options API:

<script>
import { debounce } from 'lodash'

export default {
  created() {
    this.debouncedSubmit = debounce(this.submit, 100)
  },
  methods: {
    submit(row, index) {/*...*/}
  }
}
</script>

Composition API in setup() option:

<script>
import { debounce } from 'lodash'

export default {
  setup() {
    const submit = (row, index) => {/*...*/}

    return {
      debouncedSubmit: debounce(submit, 100),
    }
  }
}
</script>

Composition API in <script setup>:

<script setup>
import { debounce } from 'lodash'

const submit = (row, index) => {/*...*/}
const debouncedSubmit = debounce(submit, 100)
</script>

Template:

<cost-code-field @change="debouncedSubmit(props.row, props.index)" />

demo

  • Related