vue 3 data() not workink setup() working but why? - vuejs3

What is the difference? Why does one work and the other why not? The latter is included in the documentation but does not work. Is there something wrong with the webpack? I use laravel-mix
This code snippet work:
<template>
<button #click="log">click me<button>
</template>
<script>
export default {
setup() {
const log = () => console.log('run');
return {
log
};
}
}
</script>
This code snippet didn't working:
<template>
<button #click="log">click me<button>
</template>
<script>
export default {
methods: {
log() {
console.log('run');
}
}
}
</script>

Both should not work, because you forgot to close your button and that should lead to a compiler error.
Anyways, if you fix the errors in your markup both should work.
Here you are using Vue's Options Api.
<template>
<button #click="log">click me</button>
</template>
<script>
export default {
methods: {
log() {
console.log("run");
},
},
};
</script>
Here you are using Vue's Composition Api
<template>
<button #click="log">click me</button>
</template>
<script>
export default {
setup() {
const log = () => console.log('run')
return {
log
}
}
};
</script>
Maybe there's a chance you also disabled the Options Api in your webpack.mix.js?

Related

Getting element instance from child component in the parent and code is runnign twice

Trying to get data from a child component inside the parent component but its not working proper, the event is fired but runs twice, first time with no data(mouseEvents info)
EDIT
parent.vue
<template>
<div>
<button #clickEventChild="formSubmit"/>
</div>
</template>
<script>
export default {
methods: {
const formSubmit = e => {
console.log(e) //first time it will output mouseEvents...
if (e.submitter) { // prevents running twice
e.submitter.classList.add('active');
...
}
}
}
}
</script>
child.vue
<template>
<div>
<button class="btn" #click="handleClick">
save
</button>
</div>
</template>
<script>
export default {
emits:['clickEventChild'],
methods: {
handleClick: function(e) {
this.$emit("clickEventChild", e);
}
}
}
</script>

Vue3 Wait till Child Component is Mounted

I want to wait for a child component to mount before rendering a tooltip. While waiting, I will have a placeholder to display to the user.
An example component would be:
<template>
<div v-if="!mounted">Loading...</div>
<child-component></child-component>
</template>
<script>
export default defineComponent({
setup() {
const mounted = ref(false);
return {
mounted
}
}
});
</script>
After doing research, it looks like Vue2 supports life-cycle hooks on the components, which would change the above code to:
<template>
<div v-if="!mounted">Loading...</div>
<child-component #hook:mounted="mountedCheck"></child-component>
</template>
<script>
export default {
export default defineComponent({
setup() {
const mounted = ref(false);
const mountedCheck = () = {
mounted.value = true;
}
return {
mounted, mountedCheck
}
}
});
}
However, I cant seem to get the #hook:mounted to work. Is there something similar in Vue3, or am I missing something?
The syntax has changed in Vue3. Now you need to use #vue:mounted instead of #hook:mounted.
See Vue 3 Migration Guide - VNode Lifecycle Events for details
Also, keep in mind that some event names like destroyed and beforeDestroy have been renamed to unmounted and beforeUnmount respectively in Vue3
The child component needs to emit that hook
child:
<script>
export default defineComponent({
setup() {
onMounted(() => {
const { emit } = getCurrentInstance();
emit('mounted');
})
return {};
}
});
</script>
parent
<template>
<div v-if="!mounted">Loading...</div>
<child-component #mounted="mountedCheck"></child-component>
</template>

How to correctly reference HTMLElement in VueJs component

I have simple component, that wraps text area. And I've another simple component, that renders a button. I want to set focus to text area when clicking the button.
This simplified example fails:
<template>
<MyCommand #resize="testResize" />
<TextArea ref="refElement" />
</template>
<script lang="ts">
// ...
export default defineComponent({
name: 'SimpleComponent',
setup(props, context) {
const refElement = ref<HTMLElement | null>(null)
const testResize = () => {
console.log('resize test')
if (refElement.value !== null) {
refElement.value.focus()
}
}
return {
refElement,
testResize,
}
}
</script>
TextArea is very simple component, some input normalization, oversimplified:
<template>
<textarea v-model.trim="value" />
</template>
I get "resize test" in console, so testResize method is running, but refElement is null.
When referencing component, not a HTML element, component type should be DefineComponent instead of HTMLElement.
Wrapped element could be referenced through $el property:
<template>
<MyCommand #resize="testResize" />
<TextArea ref="refElement" />
</template>
<script lang="ts">
// ...
export default defineComponent({
name: 'SimpleComponent',
setup(props, context) {
const refElement = ref<DefineComponent>()
const testResize = () => {
console.log('resize test')
if (refElement.value) {
refElement.value.$el.focus()
}
}
return {
refElement,
testResize,
}
}
</script>
I'm not sure if this is the "best practice", it looks to me as a hack. If anyone knows better solution, please comment.
You're missing the refElement in your return
return {
testResize, refElement
}
Update
If you are dealing with a component it becomes a bit trickier. while you can use refElement.value.$el, I'd say it's not a good idea. This will only work if the component has the first child the textarea. This will make for a brittle implementation, where if you need to change that at some point, it will break. IMHO, you're better off passing the ref as a prop to the child component. This is also not best practice, because you're supposed to pass props down and emit events up, but that that would be quite the overhead to implement. Passing ref as a prop comes with it's own issues though. If you have a ref in the template, it gets automagicaly converted from the ref/propxy to a value. To get around that, you can pass the prop in a function refElement: () => refElement in the setup(can't do it template). Of course, YMMV, but this is the path I'd chose.
const app = Vue.createApp({
setup(props, context) {
const refElement = Vue.ref(null)
const testResize = () => {
if (refElement.value !== null) {
refElement.value.focus()
}
}
return {
testResize,
refElement: () => refElement
}
}
});
app.component("text-area", {
template: `<textarea ref="taRef"></textarea></div></div>`,
props: {
textarearef: {
type: Function
}
},
setup(props) {
const taRef = props.textarearef()
return {
taRef
}
}
})
app.mount("#app");
<script src="https://unpkg.com/vue#3.0.3/dist/vue.global.prod.js"></script>
<div id="app">
<text-area :textarearef="refElement"></text-area>
<button #click="testResize">🦄</button>
</div>

Vue 3 reuse composition API in separate file

I'm new to Vue.js and I'm trying to do the following.
I have a working Vue 3 application where I'm testing the composition Api feature.
My App.vue file is simple and has the following:
<template>
<div>
<p>Counter: {{ counter }}</p>
<button #click="increaseCounter()">Increase counter</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const counter = ref(3);
function increaseCounter() {
counter.value++;
}
return {
counter,
increaseCounter
};
}
};
</script>
Now everything is working with this code.
What I want to do is to separate the script code into a new separate file and import in the App.vue to reuse it. I tried the following but with no luck:
<template>
<div>
<p> Msg: {{ msg }} </p>
<p>Counter: {{ counterApi.counter }}</p>
<button #click="counterApi.increaseCounter()">Increase counter</button>
</div>
</template>
<script>
import { counterApi } from "./counter-api.js";
export default {
created: () => {
counterApi.increaseCounter;
}
};
</script>
counter-api.js
import { ref } from "vue";
export default {
setup() {
const counter = ref(3);
function increaseCounter() {
counter.value++;
}
return {
counter,
increaseCounter
};
}
};
I'm getting the following error
Cannot read property 'increaseCounter' of undefined.
Thanks in advance
You did not showed what is inside counter-api.js. Based on import you have, it should be something like this:
export function CounterApi {
const counter = ref(3);
function increaseCounter() {
counter.value++;
}
return {
counter,
increaseCounter
};
}
Or if we use more ES6:
export const CounterApi = () {
const counter = ref(3);
const increaseCounter = () {
counter.value++;
}
return {
counter,
increaseCounter
};
}
You imported file, but it's not enough because you did nothing with it. Instead:
<template>
<div>
<p>Counter: {{ counter }}</p>
<button #click="increaseCounter()">Increase counter</button>
</div>
</template>
<script>
import { CounterApi } from "./counter-api";
export default {
setup() {
const { counter, increaseCounter } = CounterApi();
return {
counter,
increaseCounter
};
}
};
</script>
Or
<template>
<div>
<p>Counter: {{ someName.counter }}</p>
<button #click="someName.increaseCounter()">Increase counter</button>
</div>
</template>
<script>
import { CounterApi } from "./counter-api";
export default {
setup() {
const someName = CounterApi(); // use all under single const
return { someName };
}
};
</script>
There is plenty articles about composition API, for example: https://vueschool.io/articles/vuejs-tutorials/state-management-with-composition-api/
Also it is good idea and common convention to name this composable as useCounterApi and then const counterApi = useCounterApi() or with dectruction const { something, something } = useCounterApi()
It is all in official docs:
https://v3.vuejs.org/api/composition-api.html

Vue.js v-show not working after axios

v-show is not working as I expected, I'm guessing it is because this.conversation.hidden is not set when browser is rendered since it is coming with async call. In this case, how can I make it work?
Thanks in progress! And tell me if my description is insufficientã… 
<template>
<div>
<div v-show="conversation.hidden">hidden</div>
<div v-show="!conversation.hidden">not hidden</div>
<button #click="conversation.hidden = false">Click Me!</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
conversation: {},
};
},
created() {
axios.get('request_something', {
}).then((response) => {
this.conversation = response.data;
this.conversation.hidden = true;
});
},
};
</script>
---------SOLVED--------------
as #thanksd mentioned in my comment, using Vue.set() will solve this problem. I should have explained more briefly with my question, but he knew what I was looking for. :) Thanks to others who answered me too.
If you add the hidden to your conversation object it will work. Vue will look for the object and if the object is undefined it won't work.
conversation: {
hidden: false
}
take a look at jsFiddle example
https://jsfiddle.net/eywraw8t/141516/
Perhaps you could try conditional rendering:
<template>
<div v-cloak>
<div v-if="conversation.hidden">Hidden</div>
<div v-else>Not Hidden</div>
<button #click="toggleConversation">Click Me!</button>
</div>
</template>
<script>
export default {
data() {
return {
conversation: {
data: [],
hidden: true
}
}
},
created() {
axios.get('/endpoint').then((response) => {
if (response.data) {
this.conversation.data = response.data
this.conversation.hidden = false
}
})
},
methods: {
toggleConversation() {
this.conversation.hidden = !this.conversation.hidden
}
}
}
</script>

Resources