Change view by using computed property - vue.js

I have component of ViewControls, where have switchControls with which I can get the index of the clicked button. For example, i clicked first btn, and snippetValue will be 0, and so on.
<template>
<SwitchControls v-model="snippetValue">
<button>
<i class="icon list" />
</button>
<button>
<i class="icon grid" />
</button>
</SwitchControls>
</template>
Question, how can i bind to a snippetValue (0 - 'list', 1 - 'grid') and emit params
emits: [ "update:modelValue" ],
setup (props, { emit }) {
const snippetValue = ref(0)
const currentValue = computed({
get: () => props.modelValue,
set: (val: number) => {
if (val === 0) {
emit("update:modelValue", "list")
}
else {
emit("update:modelValue", "grid")
}
},
})
},
parent.vue
<ViewControls v-model="snippetVariant"/>

Try to bind the v-model directive from SwitchControls directly with computed property currentValue :
<template>
<SwitchControls v-model="currentValue">
<button>
<i class="icon list" />
</button>
<button>
<i class="icon grid" />
</button>
</SwitchControls>
</template>
emits: [ "update:modelValue" ],
setup (props, { emit }) {
const currentValue = computed({
get: () => props.modelValue,
set: (val: number) => {
emit("update:modelValue", val === 0 ? "list" : "grid")
},
})
},

Related

Prop being passed but not updating the component

So I have a function called updateAll(data.items) which should take the array and randomly assign a new name, it appears the data.items is updating when I view in vue dev tools and is being passed to the component but not actually updating the browser
I've also added in my buildData.js this is being used for the same app but in react so was trying to keep it as general as I could
//HomeView.vue
<script setup>
import { reactive, watch } from "vue";
import Hero from '#/components/Hero.vue'
import FunctionButton from '#/components/FunctionButton.vue'
import ItemsContainer from '#/components/ItemContainer.vue';
import * as dataFunc from '#/data/buildData'
const data = reactive({
items: JSON.parse(localStorage.getItem('items')) || []
})
console.log(data.items)
watch(() => data.items,(newValue) => {
localStorage.setItem('items', JSON.stringify(newValue))
}, {deep: true});
</script>
<template>
<div class='w-2/3 mx-auto text-center'>
<Hero HeroText="The one stop shop to store recipes!" />
<main>
<h1 class="text-5xl font-extrabold dark:text-white">Recipe Manager</h1>
<div class='functions gap-4 grid grid-cols-2 md:grid-cols-3 mt-8 mx-auto w-fit'>
<FunctionButton name="Add new recipe" #click="() => data.items = [...data.items,...dataFunc.buildData(1)]"/>
<FunctionButton name="Add 100 recipes" #click="() => data.items = [...data.items,...dataFunc.buildData(100)]"/>
<FunctionButton name="Add 1000 recipes" #click="() => data.items = [...data.items,...dataFunc.buildData(1000)]"/>
<FunctionButton name="Delete all recipes" #click="() => data.items = dataFunc.deleteAll()"/>
<FunctionButton name="Edit all recipes" #click="() => data.items = dataFunc.updateAll(data.items)"/>
</div>
<ItemsContainer :items="data.items"/>
</main>
</div>
</template>
//ItemContainer.vue
<script setup>
import Item from '#/components/Item.vue';
const props = defineProps({
items: {type: Array, default: () => []},
})
</script>
<template>
<div
class='items-container w-full md:w-max bg-gray-800 dark:bg-gray-800 text-white dark:text-black mx-auto mt-12 p-4 space-y-4'>
<Item
v-for="item in items"
v-if="items.length > 0"
:key="item.id"
:meal="item"
/>
<p v-else class='text-white'>No items to display</p>
</div>
</template>
I tried to make this as generic as possible as trying to make a similar app on Vue and React so they could share this
import { v4 as uuidv4 } from 'uuid';
function create(){
const data = {
id: uuidv4(),
category: category[random(category.length)],
name: meals[random(meals.length)],
prepTime: randomTime(),
cookTime: randomTime(),
isFav: false
}
return data
}
function random(max){
return Math.round(Math.random() * 1000) % max;
}
const meals = [
"Roast Chicken", "Omelette", "Steak Pie", "Beef Stir Fry", "Fish And Chips", "Tomato And Prawn Risotto", "Pepperoni Pizza", "Cheesy Nachos", "Fajitas", "Baked Potato",
"Full English Breakfast", "Pancakes", "Chocolate Brownies", "Meatballs With Red Pepper Sauce", "Chicken Cesar Salad", "Beef Burger", "Chips","Macaroni Cheese", "Fillet Steak", "Chicken Wings",
"BBQ Ribs", "Tomato Soup", "Prawn Dim Sum", "Pork Gyozas", "Tomato Bruschetta", "Spring Rolls", "Beef Jerky", "Lasagne", "Spagetti Carbonara", "Salmon And Potatoes"
]
const category = [
"Breakfast", "Lunch", "Dinner"
]
function randomTime(){
return Math.round(Math.random() * 30)
}
function buildData(count){
const data = new Array(count);
for(let i = 0; i<count; i++){
data[i] = create();
}
return data;
}
function updateAll(currentArray){
return currentArray.map((item) => {
return{...item, name: meals[random(meals.length)], }
})
}
function updateOne(item){
return{...item, name: meals[random(meals.length)], }
}
function favouriteAll(currentArray = []){
return currentArray.map((item) => {
return {...item, isFav: true}
})
}
function deleteAll(){
const data = []
return data;
}
export {buildData, deleteAll, favouriteAll, updateAll, updateOne};
Check the console for errors.
If I take the functionality from HomeView.vue, your ItemContainer.vue and an Item.vue (which you didn't provided), then it works.
Check the working Vue SFC Playground
The code:
App.vue
<script setup>
import { ref, watch, reactive } from 'vue'
import ItemsContainer from './ItemContainer.vue';
const data = reactive({
items: []
})
console.log(data.items)
watch(() => data.items, (newValue) => {
alert(JSON.stringify(newValue))
}, {deep: true});
const addItem = () => {
data.items.push({ id: data.items.length, name: 'New Item'})
}
</script>
<template>
<ItemsContainer :items="data.items"/><br />
<button type="button" #click="addItem()">Add</button>
</template>
ItemContainer.vue is the same.
Item.vue
<script setup>
const props = defineProps({
meal: {
type: Object,
default: {undefined}
}
})
</script>
<template>
<div>
{{JSON.stringify(meal)}}<br />
</div>
</template>
UPDATE
I've updated the playground with your functions and it still does work well. The problem is somewhere else.

Why the text isn’t updated in Vue3?

I’m trying to display a name dynamically, but I get the same name forEach element. What I’m trying to do is:
<template>
<div class="app__projects">
<div
class="app__projects__container"
v-for="project in visibleProjects"
:key="project.id"
:id="project.id"
>
<div class="app__projects__image">
<img
:src="project.imgUrl"
alt="Project Image"
width="570"
height="320"
loading="lazy"
/>
</div>
<div class="app__projects__content">
<h3>{{ project.name }}</h3>
<p>
{{ project.description }}
</p>
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ displayNameButton }}
</a>
<hr class="app__projects__content--spacer" />
</div>
</div>
<button
v-if="showMoreProjectsButton"
class="app__projects__showMoreButton"
#click="loadMoreProjects"
>
show more projects
</button>
</div>
</template>
On the I'm trying to display a name dynamically, and all the time the same name is displayed, but I want to display the name based on the computed property that I wrote below.
Here is the visibleProjects:
const visibleProjects = computed(() => {
return storeProjects.projects.slice(0, maxProjectsShown.value);
});
I’m trying to iterate through an array of objects from the store like:
const displayNameButton = computed(() => {
const isObjPresent = storeProjects.projects.find((o => o.wordpress === 'yes')).wordpress;
console.log(isObjPresent);
if (isObjPresent === 'yes') return 'See Website';
else if (!isObjPresent) return 'See code';
})
The array of objects from the store is:
import { defineStore } from 'pinia';
import { v4 as uuidv4 } from 'uuid';
export const useProjectsStore = defineStore({
id: 'projects',
state: () => {
return {
projects: [
{
id: uuidv4(),
imgUrl: lightImg,
name: 'use this',
description:
'track of this',
wordpress: false,
},
{
id: uuidv4(),
imgUrl: recogn,
name: 'deep lear',
description:
'I tried my best',
wordpress: ‘yes’,
},
...
{},
{},
],
};
},
});
So the problem is with your computed property. It will always return the same value because there is no input based on which the function can determine which string should it returns. Based on the code you already have I think you should write a method that will return desired string.
const displayNameButton = (project) => {
return (project.wordpress === 'yes') ? 'See Website' : 'See code';
})
and in the template
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ displayNameButton(project) }}
</a>
OR you can modify your visibleProjects:
const visibleProjects = computed(() => {
return storeProjects.projects.slice(0, maxProjectsShown.value).map((e) => {
const project = {...e};
project.wordpress = (project.wordpress === 'yes') ? 'See Website' : 'See code';
return project;
});
});
and in the template
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ project.wordpress }}
</a>

How to use Axios with Vue 3 Composition API

I am attempting to build a Pokemon filtered search app with Vue 3 and Composition API based on the following tutorial: https://www.youtube.com/watch?v=QJhqr7jqxVo. (GitHub: https://github.com/ErikCH/PokemonVue)
The fetch method used in the search component includes a reduce() function to handle urlIdLookup based on a specific id assigned to each Pokemon in the API response:
const state = reactive({
pokemons: [],
filteredPokemon: computed(()=> updatePokemon()),
text: "",
urlIdLookup: {}
});
fetch("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((res) => res.json())
.then((data) => {
console.log(data);
state.pokemons = data.results;
state.urlIdLookup = data.results.reduce((acc, cur, idx)=>
acc = {...acc, [cur.name]:idx+1 }
,{})
console.log('url',state.urlIdLookup+1)
});
urlIdLookup is then passed into the route used to display selected Pokemon info:
<div
class="ml-4 text-2xl text-blue-400"
v-for="(pokemon, idx) in filteredPokemon"
:key="idx"
>
<router-link :to="`/about/${urlIdLookup[pokemon.name]}`">
{{ pokemon.name }}
</router-link>
</div>
Instead of using the above fetch setup, I wish to use Axios to handle the request and response from the Pokemon API. After installing Axios in the project and importing it into the component, I added a new fetchPokemon method:
const fetchPokemon = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?offset=0')
.then(response => {
state.pokemons = response.data
})
}
onMounted(() => {
fetchPokemon()
})
While using Axios in this new fetch method, I want to handle urlIdLookup similar to the previous fetch setup, but without using the reduce() method and de-structured accumulator, if possible. How can I go about using Axios to retrieve the urlId of each Pokemon, then pass that urlId into the "about" route in the template?
Here is the full component:
<template>
<div class="w-full flex justify-center">
<input placeholder="Enter Pokemon here" type="text"
class="mt-10 p-2 border-blue-500 border-2" v-model="text" />
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div
class="ml-4 text-2xl text-blue-400"
v-for="(pokemon, idx) in filteredPokemon"
:key="idx"
>
<router-link :to="`/about/${urlIdLookup[pokemon.name]}`">
{{ pokemon.name }}
</router-link>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { reactive, toRefs, computed, onMounted } from "vue";
export default {
setup() {
const state = reactive({
pokemons: [],
filteredPokemon: computed(()=> updatePokemon()),
text: "",
urlIdLookup: {}
});
const fetchPokemon = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?offset=0')
.then(response => {
state.pokemons = response.data
})
}
onMounted(() => {
fetchPokemon()
})
// fetch("https://pokeapi.co/api/v2/pokemon?offset=0")
// .then((res) => res.json())
// .then((data) => {
// console.log(data);
// state.pokemons = data.results;
// state.urlIdLookup = data.results.reduce((acc, cur, idx)=>
// acc = {...acc, [cur.name]:idx+1 }
// ,{})
// console.log('url',state.urlIdLookup+1)
// });
function updatePokemon(){
if(!state.text){
return []
}
return state.pokemons.filter((pokemon)=>
pokemon.name.includes(state.text)
)
}
return { ...toRefs(state), fetchPokemon, updatePokemon };
}
};
</script>
If I understood you correctly take a look at following snippet:
const { reactive, toRefs, computed, onMounted } = Vue
const { axioss } = axios
const app = Vue.createApp({
setup() {
const state = reactive({
pokemons: [],
filteredPokemon: computed(() => updatePokemon()),
text: "",
urlIdLookup: {},
});
const fetchPokemon = () => {
axios
.get("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((response) => {
state.pokemons = response.data.results; // 👈 get just results
});
};
fetchPokemon();
// 👇 function to get index
const getPokemonId = (item) => {
return state.pokemons.findIndex((p) => p.name === item);
};
function updatePokemon() {
if (!state.text) {
return [];
}
return state.pokemons.filter((pokemon) =>
pokemon.name.includes(state.text)
);
}
// 👇 return new function
return { ...toRefs(state), fetchPokemon, updatePokemon, getPokemonId };
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.26.1/axios.min.js" integrity="sha512-bPh3uwgU5qEMipS/VOmRqynnMXGGSRv+72H/N260MQeXZIK4PG48401Bsby9Nq5P5fz7hy5UGNmC/W1Z51h2GQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="demo">
<div class="w-full flex justify-center">
<input
placeholder="Enter Pokemon here"
type="text"
class="mt-10 p-2 border-blue-500 border-2"
v-model="text"
/>
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div
class="ml-4 text-2xl text-blue-400"
v-for="(pokemon, i) in filteredPokemon"
:key="i"
>
<!-- // 👇 call function to get index -->
<router-link :to="`/about/${getPokemonId(pokemon.name)}`">
{{ pokemon.name }} - id {{ getPokemonId(pokemon.name) }}
</router-link>
</div>
</div>
</div>
it seem id is not necessary, why not use name be id. if you want use interger
be must, you can foreach results set index be id to each item, then
<router-link :to="`/about/${pokemon.id}`">
{{ pokemon.name }}
</router-link>

How to pass props to input value in Vuejs

I have a parent component as a Cart. Here I defined quantity and I want to pass this quantity to the child component's input value which is Counter. So here how I am passing it and here is my parent component, Cart:
<Counter quantity="item.quantity"/>
And here is my child component, Counter:
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="quantity == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="quantity"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
methods: {
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
}
}
</script>
I am quite new in Vue, so maybe I am doing something wrong when I pass the props. So could you help me about this?
Try (with the : colon sign)
<Counter :quantity="item.quantity"/>
Before you were just passing the string "item.quanity"
I see you're modifying your prop directly:
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
This is not how you do it in Vue. You need to use two way binding.
countUp() {
this.$emit('input', this.quantity+1)
}
countDown() {
this.$emit('input', this.quantity-1)
}
and in your parent component:
<Counter :quantity="item.quantity" #input="(payload) => {item.quantity = payload}"/>
By the way, the Vue styleguide recommends to use multi-word component names: https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential (Cart = bad, MyCart = good)
We cannot change the value that we get from props, so I created a variable and put props there when mounting
Try it
<Counter :quantity="item.quantity"/>
and
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="sum == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="sum"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
data: () => ({
sum: 0
}),
mounted() {
this.sum = this.quantity;
},
methods: {
countUp() {
this.sum++;
},
countDown() {
if(this.sum > 0) {
this.sum--;
}
},
}
}
</script>

multiple dynamic countdown timers vue js on laravel

I would like to have a countdown timer for each of my auction items, these auctions can and will be loaded dynamically.
Ideally {{ auction.total_bids }} would be the countdown, this will need to be reset to a value i can set on a click event.
My code so far, is as followed:
<template>
<div>
<h1>Live Auctions</h1>
<ul class="row">
<li class="col-lg-4" v-for="(auction, key, index) in auctions" :key="auction.id">
<span>{{ auction.name }} ({{ auction.id }})</span><br />
<span ref="bitTime">{{ auction.bid_time }}</span><br />
<span ref="totalBids">{{ auction.total_bids }}</span><br />
<span ref="user">{{ auction.username }}</span><br />
<button ref="newBid" #click="bidOnThis(auction.id, key)">Bid on this item</button>
</li>
</ul>
</div>
</template>
<script>
export default {
props : {
date : {
type: Number,
coerce: str => Math.trunc(Date.parse(str) / 1000)
}
},
data() {
return {
auctions: [],
newBid: '',
totalBids: '',
user: []
};
},
created() {
axios.get('/auctions').then(result => {
this.auctions = result.data
});
axios.get('/getuser').then(result => {
this.user = result.data;
});
window.Echo.channel('auction').listen('AuctionUpdated', ({ auction, username, key }) => {
this.$refs.totalBids[key].innerHTML = parseInt(this.$refs.totalBids[key].innerHTML) + 1 ;
this.$refs.user[key].innerHTML = username ;
});
},
methods: {
bidOnThis(id, key) {
axios.post('/auctions', { id: id, key: key });
this.$refs.totalBids[key].innerHTML = parseInt(this.$refs.totalBids[key].innerHTML) + 1 ;
this.$refs.user[key].innerHTML = this.user.username ;
}
}
};