Vue 3 Compositions API ref in v-for - vuejs3

Hej!
I'm trying to improve my due skills. I decided to make a simple dice game but I'm failing at the beginning.
I have two components. The Main Game and a dice component. In the main App component, I include the dice component and iterate over it 5 times to create 5 dices. I also add a button to roll all the dices:
<template>
<div class="center">
<Dice v-for="(dice, i) in 5" ref="diceChildren" :initialNumber="i" />
</div>
<div class="center">
<button #click="rollDice">Würfeln</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Dice from './components/Dice.vue'
const diceChildren = ref([]);
const rollDice = () => {
console.log(diceChildren);
}
</script>
I followed the official instructions here: https://vuejs.org/guide/essentials/template-refs.html#refs-inside-v-for and I expected a filled array with dice refs but the diceChildren array is empty.
I need the reference to the dices to roll the dice and to get rolled number.
The get things completed, this is the code of the dice component:
<template>
<div class="dice">
<img class="dice-image" :src="getImagePath()" alt="Bild eines Würfels"/>
</div>
</template>
<script setup>
import { defineProps, ref } from 'vue'
const props = defineProps({
initialNumber: Number,
});
const number = ref(props.initialNumber + 1);
const rollDice = () => {
number.value = Math.floor(Math.random() * 6 + 1)
}
const getImagePath = () => `dice_${number.value}.png`;
defineExpose({
rollDice,
});
</script>
Thank you so much for help. I exhausted many hours hitherto.
I also tried to take the refs insight template:
<template>
<div class="center">
<template v-for="(dice, i) in 5" ref="diceChildren" />
<Dice :initialNumber="i" />
</template>
</div>
<div class="center">
<button #click="rollDice">Würfeln</button>
</div>
</template>

Related

Composition API + script setup Dynamic component

I want to implement simple tabs using script setup and composition API
<script setup>
import Timeline from './Timeline.vue';
import Profile from './Profile.vue';
import Groups from './Groups.vue';
const currentTab = ref('Timeline')
const tabs = ref(['Timeline', 'Profile', 'Groups'])
</script>
<template>
<div class="tabs">
<div v-for="tab in tabs"
:key="tab"
#click="currentTab = tab" v-text="tab"
<component :is="currentTab"></component>
</div>
</template>
But this code will only result <timeline></timeline> instead of the actual content of the Timeline component.
As mentioned here when using the <script setup>-method you need to pass the actual object of the component to the :is instead of the string.
See this Example here
Here is also the code itself:
<script setup>
import Timeline from './Timeline.vue';
import Profile from './Profile.vue';
import Groups from './Groups.vue';
import { shallowRef } from 'vue';
const currentTab = shallowRef(Timeline);
const tabs = [Timeline, Profile, Groups];
</script>
<template>
<div class="tabs">
<div v-for="tab in tabs"
:key="tab"
#click="currentTab = tab" v-text="tab.__name">
</div>
</div>
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
</template>

VueJs calling :key in method

im new to Vue and trying myself in Vue3 and Vuetify.
Similar to this Post im trying to :key or ref a vue-signature-pad in a for loop.
I initialize an empty Array and push a new element, which adds a new signature.
<template>
<div>
<v-container>
<div class="row" v-for="(input, index) in inputs">
<v-container>
<VueSignaturePad
id="signature"
width="100%"
height="300px"
ref="signaturePad"
/>
</v-container>
<div class="buttons">
<button #click="undo">Undo</button>
<button #click="save">Save</button>
</div>
</div>
<v-row justify="center">
<v-btn #click="addRow"
class="ma-2"
color="blue-grey"
icon="mdi-plus-circle-outline"
></v-btn>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
data: () => ({
inputs: [],
}),
methods: {
addRow() {
this.inputs.push({
name: ''
})
},
undo() {
this.$refs.signaturePad.undoSignature();
},
save() {
const { isEmpty, data } = this.$refs.signaturePad.saveSignature();
alert("Open DevTools see the save data.");
console.log(isEmpty);
console.log(data);
}
}
}
</script>
<style scope>
</style>
In the Post i already mentioned, i tried the Solution from Mathieu Janio.
So if i understand everything right, ref is not working and i have to use :key on the div itself.
So i tried his Solution
<template>
<div
class="row"
v-for="(applicants, index) in applicant_details"
:key="index"
>
<div class="col-sm-4">
<div class="form-group">
<p>Signature for {{ applicants.first_name }}</p>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<VueSignaturePad ref="" />
<button type="button" #click="undo" class="btn btn-warning">
Undo
</button>
</div>
</div>
</div>
</template>
<script setup>
const applicant_details = [
{ first_name: 'john', signature: '' },
];
const undo = () => {
//will undo the signature
};
const save_signature = () => {
//Save the signature
};
</script>
But now im stuck at calling the right "undo"
The signatepad github example uses ref: at the single signaturepad, which is ok.
but not working at the forloop
How do i call now the right function in "undo" for the solution above?
I tried using "this.$.vnode.key" but this gives me an error.
"Uncaught TypeError: Cannot read properties of undefined (reading '$')".
I figured my first way out.
Heres what i have done.
The Signaturepad got an index on the ref.
<VueSignaturePad
:id="'signaturePad' + index"
width="100%"
height="300px"
:ref="'signaturePad' + index"
:options="options"
/>
The "undo" button is giving it "ref" with
<button #click="undo(`signaturePad${index}`)">Undo</button>
And the undo function itself has the first item in its ref array
undo(ref) {
this.$refs[ref][0].undoSignature();
}
Maybe someone has some more efficient way. Feel free :)

Passing props that is an array to 2 different components

This is my 3rd day working with Vue CLI, and I want to pass props that is an array to 2 different components. The components are called Products and Modal. The Modal component is dependent on the Products component. When I pass props to both components inside my App.vue, I don't want both components to render in my App.vue. Like I said, my Modal component is dependent on my Products component.
My overall goal is to display the product title, photo, and description in my modal.
The issue I have is I make a get request in my App.vue, which i fill an empty array and then pass that to Products component. If i pass the array as props to both Products and Modal, I will get an extra modal to render at app level which does make sense, but I don't want that.
Im still getting used to how Vue works and tips or help would be much appreciated.
This could be a very obvious answer that i'm just overlooking, but i'm learning, so please understand that.
App.Vue (I make my get request here and fill an empty array to then have that array passed down as props to the two components):
<template>
<div id="app">
<all-products v-bind:newProductsArray="newProductsArray"></all-products>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import AllProducts from './components/AllProducts.vue'
import Modal from './components/Modal.vue'
//import so I can use vue resource for an http request
import VueResource from 'vue-resource'
Vue.use(VueResource)
//export components to be rendered by main.js then by the DOM
export default {
components: {
'all-products': AllProducts
},
data() {
return {
//empty products array to be filled after get request
products: [],
//declare new array to be set to products array, then passed as props. I do this because without another array,
//all products component will receiev an empty array before it can be filled by the http request.
newProductsArray: []
}
},
//request to retrieve all products from API using Created
created() {
//http request using vue resource from dependencies
this.$http.get('https://tap-on-it-exercise-backend.herokuapp.com/products').then(function(data) {
//fill products array by grabbing the data, slicing ot, then setting it to products array
this.products = data.body.slice(0, data.body.length)
//set products array to the new array to be passed down as props to all products component.
this.newProductsArray = this.products
})
}
}
</script>
Product component (This component receives props from app.vue):
<template>
<div class="products">
<h1 class="all-products">All Products</h1>
<div v-for="product in newProductsArray" :key="product.id" class="single-product">
<div class="product-container">
<div class="row">
<div class="col-md-8 center-block">
<div class="title">{{product.title}}</div>
<img class="images" :src="product.photo_url">
<div class="price">${{product.price}}</div>
<modal/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import Modal from './Modal.vue'
//import bootstrap to be used to style buttons and modal.
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
export default {
//receive new products array as props from app
props: ['newProductsArray'],
components: {
'modal': Modal
},
data() {
return {
//set modal show to false by default
modalShow: false
}
},
methods: {
showModal() {
//toggle modal to show
this.modalShow = true
},
closeModal() {
//toggle modal to close
this.modalShow = false
}
},
}
</script>
Modal component (I want to receive the same props that products received)
<template>
<div>
<b-button #click="modalShow = !modalShow">Show Product Info</b-button>
<b-modal v-model="modalShow" title="title">
<div class="modal-body">
<img class="img-responsive" style="margin:0 auto;" src="http://placehold.it/300x340" alt="">
</div>
<p class="product-description">Description</p>
<div slot="modal-footer" class="w-100">
<b-button size="md" class="float-right" variant="warning" #click="modalShow=false">Close</b-button>
<b-button size="md" class="float-right" variant="primary" #click="modalShow=false">Like</b-button>
</div>
</b-modal>
</div>
</template>
<script>
export default {
props: ['newProductsArray'],
data() {
return {
modalShow: false
}
}
}
</script>

Pushing data to object in different component using POST

TL;DR I want to show submitted posts instantly instead of having to refresh my page
Using the Wordpress REST API I am able to create a new post without any issue. The post is being displayed as soon as the page refreshes, so what I want to do is update the posts object in my Hello.vue file as soon as I create that post so I don't need to refresh to show my newest posts.
I'm not really sure where to start - I've removed all of the experiments I've done so far (importing Post in Create, defining props, pushing to an array, reading about object reactivity on the official Vue documentation, nothing helped).
My App.js consists of the <router> object which shows Hello.vue and a component called Create which displays the Create.vue component. This is how my app currently looks like:
My App.vue file:
<template>
<div id="app">
<section class="posts">
<router-view></router-view>
<create></create>
</section>
</div>
</template>
<script>
import Create from '#/components/Create.vue'
export default {
name: 'app',
components: {
Create
}
}
</script>
<style lang="scss">
#import '../src/assets/styles/style.scss'
</style>
My Hello.vue which displays all the posts:
<template>
<div>
<section class="posts__Feed">
<ul class="posts__List">
<post v-for="item in posts" :item="item" :key="item.id"></post>
</ul>
</section>
</div>
</template>
<script>
var postsUrl = '/wp-json/wp/v2/posts/'
import Post from '#/components/Post.vue'
export default {
name: 'hello',
props: ['responseData'],
components: {
Post
},
data () {
return {
posts: []
}
},
beforeCreate () {
this.$http.get(postsUrl).then((response) => {
this.posts = response.data
})
}
}
</script>
And finally, the Create.vue file which creates the post:
<template>
<div>
<section class="posts__Create">
<form class="posts__CreateForm" v-on:submit="createPosts">
<div class="posts__CreateFormWrapper" v-bind:class="{ 'is-Loading': loading }">
<p>
<input v-model="formInfo.title" type="text" name="title" id="title" placeholder="Name" :disabled="formSent">
</p>
<p>
<textarea v-model="formInfo.content" name="content" id="content" cols="20" rows="10" maxlength="140" placeholder="Message" :disabled="formSent"></textarea>
</p>
<p>
<button :disabled="formSent">Send</button>
</p>
</div>
</form>
</section>
</div>
</template>
<script>
var postsUrl = '/wp-json/wp/v2/posts/'
export default {
name: 'create',
data () {
return {
formInfo: [],
responseData: [],
loading: false,
formSent: false
}
},
methods: {
createPosts (e) {
e.preventDefault()
var info = this.formInfo
// Check if fields are empty
if (this.formInfo.title && this.formInfo.content) {
this.loading = true
// POST
this.$http.post(postsUrl, info).then((response) => {
this.formSent = true
this.loading = false
// get body data
this.responseData = response.data
})
}
} // EOF createPosts
}
}
</script>
Any help would be much appreciated!
I ended up using an event bus as suggested by wotex. First, I've createad a file called bus.js with the below code:
import Vue from 'vue'
export const EventBus = new Vue()
Next, import bus.js to both .vue layouts using:
import { EventBus } from '#/bus.js'
Now emit the event as soon as a new post is created (this is sitting in my axios POST request inside the Create.vue file):
EventBus.$emit('newPost', this.responseData)
And finally, check if the event has happened on the other end (my Hello.vue file):
EventBus.$on('newPost', function (postData) {
Thanks for pointing me in the right direction!

getting this npm react component to work with meteor

Using react-typeahead-component
I used browserify to change it from npm into a meteor local package. Nothing is showing on the screen when i run meteor.
OptionTemplate.jsx
module.exports = React.createClass({
render: function() {
return (
<div>
<p>HELLO</p>
</div>
);
},
handleChange: function(event) {
console.log('HELLO');
}
});
main.jsx
var OptionTemplate = require('./OptionTemplate.jsx');
SearchBox = React.createClass({render() {
return (
<div className="col-xs-12 col-lg-12">
<OptionTemplate />
</div>
)}
});
You aren't using components properly. You might want to re-read the react documentation.
In your main.jsx, you are importing the component 'OptionTemplate' but you are trying to render the component 'Typeahead' and passing in 'OptionTemplate' as a prop. The 'Typeahead' component now lives in 'OptionTemplate'. Your main.jsx should like like:
var OptionTemplate = require('./OptionTemplate.jsx');
SearchBox = React.createClass({render() {
return (
<div className="col-xs-12 col-lg-12">
<OptionTemplate />
</div>
)}
});

Resources