Cannot read properties of undefined (reading '$refs') vue js - vue.js

Getting the Error Cannot read properties of undefined (reading '$refs') though I'm having a reference in the template. Does it mean I must use Vue mounted hook ?
<div class="input-wrapper">
<input type="text" id="phone" placeholder="(555) 555-5555" ref="input"/>
</div>
<script>
this.$refs.input.addEventListener('input', function () {
// some code
});
</script>

Inside root of <script> of a Vue component, in both Vue 2 and Vue 3, this is undefined:
<script>
console.log(this) // undefined
</script>
See it here.
Vue template refs can only be accessed inside any hook or method happening after the component has been mounted and before it is unmounted.
Which means the earliest you can reference this.$refs is inside mounted. And the latest is in beforeUnmount. And you can also access them in any hook or method happening between those two moments.
Considering you're attempting to add an event listener to a HTMLInputElement, I'd recommend using the v-on directive, which automatically adds the event listener on mount and removes it on unmount.
In your case:
<template>
<div class="input-wrapper">
<input
type="text"
id="phone"
placeholder="(555) 555-5555"
#input="myFn" />
</div>
</template>
<script>
export default {
methods: {
myFn(event) {
console.log(event)
}
}
}
</script>
As a side note, you should know that a regular function's this doesn't have access to the component context, unless it's an arrow function:
export default {
mounted() {
this.$refs.input.addEventListener('input', function() {
/*
* Here `this` is the context of the current function, you can't
* access methods, computed, data or props of the component.
* You'd need to make it an arrow function to access the component scope
*/
})
}
}
Whereas in any method (e.g: the above myFn), this is the component context, which has access to all component members.

Related

Calling function inside child component without an event?

Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.

Vue.js 2: How to bind to a component method?

I have a VueJS (v2) component with a private array of objects this.private.messagesReceived which I want displayed in a textarea. The array should be converted to a string by a method/function and Vue is blocking all my attempts to bind. Every attempt results in my serialization function (converting the array to a string) only being called once and never again when the data changes.
I feel there must be a way to do this without Vue.set() or some forceUpdate shenanigans.
https://jsfiddle.net/hdme34ca/
Attempt 1: Computed Methods
Here we have the problem that Vue only calls my computed method messagesReceived1 once and never again.
<script>
{
computed: {
messagesReceived1() {
console.log("This is called once and never again even when new messages arrive");
return this.private.messagesReceived.join("\n");
},
...
methods: {
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
<script>
<template>
<textarea rows="10" cols="40" v-model="messagesReceived1"></textarea>
</template
Attempt 2: Binding Methods
Here Vue decides it doesn't like moustaches inside a textarea {{ messagesReceived2() }} and balks. It also doesn't allow messagesReceived2() or messagesReceived2 in v-model.
<script>
{
methods: {
messagesReceived2() {
return this.private.messagesReceived.join("\n");
},
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
</script>
<template>
<textarea rows="10" cols="40">{{ messagesReceived2() }}</textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2()"></textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2"></textarea><!--Nope-->
</template
You can define a data variable and set its value in the function. Then bind variable with textarea, not directly with the function.

Vue child inside a parent v-if="false" won't be excuted, but except inside a Modal wrapper with default slot, why?

Please see this minium example
1
This minimum template won't throw any error since the parent is not rendering anything.
<template>
<div v-if="false">
{{ i.dont.have.the.value }}
</div>
</template>
2
Now if I create a Modal wrapper with default slot, and still using v-if
Modal.vue
<template>
<div v-if="show">
<slot />
</div>
</template>
<script>
export default {
props: {
show: Boolean,
},
};
</script>
However, this will throw Error, Why?
App.vue
<template>
<Modal :show="false">
<div>{{ i.dont.have.the.value }}</div>
</Modal>
</template>
<script>
import Modal from "#/components/Modal.vue";
export default {
components: {
Modal,
},
};
</script>
[Vue warn]: Error in render: "TypeError: Cannot read property 'dont' of undefined"
Why is this happening?
How can I create a Modal with guarantee children won't be excuted if not show?
I can't directly use v-if in the Modal component because I need a transition.
You made a mistake about the slot in App.vue because VueJS will try to resolve your i.dont.have.the.value variable based on parent scope (your slot is not conditioned)
Remember this rule from official documentation
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope.
https://v2.vuejs.org/v2/guide/components-slots.html#Compilation-Scope
Just define the variable in the right scope and you will be fine (if you define it in child component and want to expose it to parent, use a scoped slot then).

How can I use `console.log` or `console.error` in a vue template?

I have a vue component with
<form #keydown="console.error($event.target.name);">
gives
app.js:47961 [Vue warn]: Property or method "console" is not defined
on the instance but referenced during render.
window.console doesn't work either
What is the proper way to use console and window in a template to debug?
Simplest way of providing global objects to the template is to place them in computed, like this:
console: () => console. Same goes for window,
computed: {
console: () => console,
window: () => window,
}
See it here.
If you want to run it inline instead of using a method, just add this to the form:
Codepen: https://codepen.io/x84733/pen/PaxKLQ?editors=1011
<form action="/" #keydown="this.console.log($event.target.name)">
First: <input type="text" name="fname"><br>
Second: <input type="text" name="fname2"><br>
</form>
But it'd be better to use a method instead of running functions inline, so you have more control over it:
<!-- Don't forget to remove the parenthesis -->
<form action="/" #keydown="debug">
First: <input type="text" name="fname"><br>
Second: <input type="text" name="fname2"><br>
</form>
...
methods: {
debug (event) {
console.log(event.target.name)
}
}
You can use $el.ownerDocument.defaultView.console.log() inside your template
Pro: Doesn't require any component changes
Con: Ugly
Also if you want to access console from {{ }} you can use global mixin:
Vue.mixin({
computed: {
console: () => console
}
})
If using Vue 3, do:
const app = createApp(App)
app.config.globalProperties.$log = console.log
If using Vue 2, do:
Vue.prototype.$log = console.log
Use $log inside template:
<h1>{{ $log(message) }}</h1>
To not interfere with the rendering, use $log with ?? (or || if using Vue 2, since ?? is not supported in the template):
<h1>{{ $log(message) ?? message }}</h1>
You can use this.console instead console or wrap call to console in a method, i am using eslint config with rule 'no-console': 'off'
You can use computed property or methods for this case.
If you need to code it as javascript in the Vue template. you have to define console in the data.
Please check the code below.
data(){
return {
selected :"a",
log : console.log
}
}
<span>{{log(selected)}}</span>
This will make functionality of console.log available, while resolving the template.
I'd make a getter for console template variable:
get console() { return window.console; }
For Vue 3, SFC Composition API, you have to define a function and call console or alert inside that function
<script setup>
import Child from "./Child.vue";
function notify(message) {
alert(message);
}
</script>
<template>
<Child #some-event="notify('child clicked')" />
</template>

Using v-model and refs in a slot in Vue2

I have a component that takes a main <slot> from a form that is generated elsewhere in my application. I'm trying to use v-model on the form inputs but my vue component just spits out a warning about the properties not being defined, when in fact they are.
I admit it's a weird way of doing things, but it seems to be the easiest way for me to do this since my form is being generated by Symfony.
html:
<my-component>
<input ref="start" v-model="start"/>
</my-component>
my component:
<script>
export default {
data() {
start: null
},
mounted() {
console.log(this.$refs) // === {}; expected {"start":{...}}
}
}
</script>
<template>
<div>
<slot/>
... other stuff here
</div>
</template>
console log:
Property or method "start" is not defined on the instance but referenced during render
I cannot use $refs or v-model in the html. Am I doing something wrong? Or is this just not possible.
If you declare v-model="start" in the parent then it belongs to the parent and needs to be declared there. It looks like instead you declare it in the component instead as null.
If you reorder things it should work as you expect:
Parent:
<parent>
<input v-model="start" :start="start"/>
</parent>
<script>
export default {
data() {
start: null // Important to define start here if it's used in this component's html
}
}
</script>
Component:
<template>
<div>
<slot/>
... other stuff here
</div>
</template>
<script>
export default {
props: ['start'], // Receive the prop from the parent
data() {
},
mounted () {
console.log(this.start) // Should echo the value of the prop from the parent
}
}
</script>