Vuex state change does not trigger reactivity on the page - asynchronous

I'm making a captcha component using vue. I try to fetch the captcha when the component created. When I do this in a async manner, the page will not be reactive, although the state has already been updated. But I when do it in sync manner, everything is fine. So, I'm wondering why async manner won't work?
This works
<template>
<section>
<div class="captcha-container">
<div v-if="captcha" v-html="captcha.data"></div>
</div>
</section>
</template>
<script>
import { mapState, mapActions } from "Vuex";
export default {
created: function() {
this.$store.commit('setCaptcha', {id: 'xx', data:'Hi'});
},
computed: {
...mapState(["captcha"])
},
};
</script>
This does not work
<template>
<section>
<div class="captcha-container">
<div v-if="captcha" v-html="captcha.data"></div>
</div>
</section>
</template>
<script>
import { mapState, mapActions } from "Vuex";
export default {
created: function() {
setTimeout(() => {
this.$store.commit('setCaptcha', {id: 'xx', data:'Hi'});
}, 1000);
},
computed: {
...mapState(["captcha"])
},
};
</script>

Related

Vue3 prop stays undefined even when watcher sees new and old values

I'm trying to pass data from a component through the parent to another child components props but the prop stays undefined in vue tools.
I tried having another variable stored in data and setting its value in the watcher for the prop but it stays undefined aswell.
Here's a snippet of what I'm trying to do.
App.vue
<template>
<NameDisplayer :name="name" />
<NameChanger #update="(n) => updateDisplayer(n)"/>
</template>
<script>
import NameDisplayer from './NameDisplayer.vue';
import NameChanger from './NameChanger.vue';
export default {
components:{
NameDisplayer,
NameChanger
},
data: function(){
return{
name: ""
}
},
methods: {
updateDisplayer(newVal){
this.name = newVal;
}
}
}
</script>
NameDisplayer.vue
<template>
<span>{{ name }}</span> //Name is always empty
</template>
<script>
export default:{
name:"NameDisplayer",
props:{
name: String
},
watch:{
name(newV, oldV){
console.log("Watcher: ", newV, oldV); //This prints out the correct values
}
}
}
</script>
NameChanger.vue
<template>
<span></span>
<input type="text" v-model="name" />
<button #click="updateName()">Update</button>
</template>
<script>
export default{
name:"NameChanger",
data: function(){
return{
name: ""
}
},
methods:{
updateName(){
this.$emit('update', this.name);
}
}
}
</script>

Vue3 composition API and pinia | submit data then reload the data

I'm learning Vue 3 composition API and Pinia. I'm making a todo.
When I submit a todo data through Pinia, I can submit to the DB, but it won't re-render until reload the page.
Do I need to use 'watch' to watch the state todos:[] and execute fetchTodos()?
any good solution?
here both codes, hope someone can help me. Thank you in advance.
----- VUE -----
<script setup>
import { ref, onMounted } from 'vue'
import { storeToRefs } = from 'pinia'
import { useTodoStore } from '../store/todo'
const store = useTodoStore()
const { getTodos } = storeToRefs(store)
onMounted(() => {
store.fetchTodos()
})
const todo = ref('')
const initForm = () => {
todo.value = ''
}
// submit via Pinia
const onSubmitToPinia = () => {
const payload = {
todo: todo.value,
}
store.addTodoFromPinia(payload)
initForm()
store.fetchTodo()
}
</script>
<template>
<h4>TODO</h4>
<!-- form addTodo -->
<form class="row g-4">
<div class="col-auto">
<input
class="form-control"
v-model="newName"
type="text"
placeholder="todo">
</div>
<div>
<button
class="btn btn-primary"
type="button"
#click="onSubmitToPinia(payload)">
submit through pinia</button>
</div>
</form>
<!-- render data from pinia -->
<div class="todo"
v-for="getTodo in getTodoss.todo"
:key="getTodo.id">
<b class="ms-2">{{ getTodo.todo }}</b>
</div>
</template>
---- PINIA ----
import { defineStore } from 'pinia'
import axios from "axios"
export const useAboutStore = defineStore('todo',{
state: () => {
return {
todos: []
}
},
getters: {
getTodos(state) {
return state.todos
}
},
actions: {
async fetchTodos() {
try {
const data = await axios.get('http://localhost:5000/todo')
this.todos = data.data
}
catch (error) {
alert(error)
console.log(error)
}
},
addTodoFromPinia(payload) {
const path = 'http://localhost:5000/todo'
axios.post(path, payload)
}
},
})
You don't need to use storeToRefs to accomplish what you want nor do you need to watch the state of the store.
<template>
<div class="todo"
v-for="getTodo in store.todos"
:key="getTodo.id">
<b class="ms-2">{{ getTodo.todo }}</b>
</div>
</template>
If for any reason the vue complains that the array is empty put a v-if checking if the store.todos.length is != 0.
And also fix your typos.
If the problem persists show me your new code and I help you again.

How do I display a route parameter (Vue / Firebase)

I'm creating a blog with free sewing patterns as content. I'm using route parameters to receive each blog individually. However, I'm getting a blank page when trying to retrieve its data from firebase firestore. Please help.
The blog's id appears on my address bar:
http://localhost:8080/#/admin/single-pattern/4LIS362IEWa7RKEv79g8
But it renders a blank page. I cant see my blog content.
This is my route path code. I've added a parameter of :id in my singlepattern. The SinglePattern component is where I will get the individual blog's data:
{
path: "/admin",
name: "admin",
component: Admin,
meta: {
auth: true,
},
children: [
{
path: "dashboard",
name: "dashboard",
component: Dashboard,
},
{
path: "manage-patterns",
name: "manage-patterns",
component: ManagePatterns,
},
{
path: "single-pattern/:id",
name: "single-pattern",
component: SinglePattern,
},
],
},
Here is my "ListPattern" component's code. ListPattern is where all my sewing blogs are displayed.
<template>
<div class="list-blogs">
<h1>LIST BLOG TITLES</h1>
<br />
<input type="text" v-model="search" placeholder="search blogs" />
<div
class="blog-cover"
v-for="pattern in filteredPatterns"
:key="pattern.id"
>
<div>
<router-link v-bind:to="'/admin/single-pattern/' + pattern.id">
<h3 style="cursor: pointer" v-rainbow>
{{ pattern.title | uppercase }}
</h3></router-link
>
</div>
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description | snippet"
></p>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../mixins/searchMixin";
// Basic Use - Covers most scenarios
import { VueEditor } from "vue2-editor";
import Quill from "quill";
const AlignStyle = Quill.import("attributors/style/align");
Quill.register(AlignStyle, true);
// import $ from "jquery";
import Swal from "sweetalert2";
window.Swal = Swal;
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
});
window.Toast = Toast;
export default {
name: "ManagePatterns",
components: { VueEditor },
data() {
return {
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
search: "",
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
computed: {},
},
};
</script>
And this is my 'SinglePattern' component where the clicked blog/pattern is displayed.
<template>
<div class="single-pattern">
<div class="blog-cover">
<div>
</div>
<div v-if="pattern">
<h3 style="cursor: pointer">
{{ pattern.title }}
</h3>
<div v-if="pattern.description">
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description"
></p>
</div>
</div>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../../mixins/searchMixin";
export default {
data() {
return {
id: this.$route.params.id,
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
mixins: [searchMixin],
created() {
console.log(this.$route.params.id);
var pat = this;
firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id)
.get()
.then(function(doc) {
if (doc.exists) {
pat.pattern = doc.data().pattern;
} else {
console.log("no such doc");
}
});
},
methods: {},
};
</script>
It works. I just had to change the code in my created() hook in 'SingePattern' component.
created() {
console.log(this.$route.params.id);
var docRef = firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id);
docRef
.get()
.then((doc) => {
if (doc.exists) {
this.pattern = doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch((error) => {
console.log("Error getting document:", error);
});

Vue.js how can i loop throw an array to add components to the dom according to array items

I am making an app that communicate with an api and fetch data,home page changes every day so i can't just add static components to it,
i need to create it according to the data that comes from the api.
i have a component for the home page called Home.vue
this component can have one or more Carousels depending on the data that i'am fetching.
i also have Carousel.vue which is responsible about displaying images and it had it's own props.
the question is :
How to add component to the dom from loop
this is Home.vue where i am making the loop :
<template>
<div>
<!--I Need The Loop right here-->
</div>
</template>
<script>
export default {
components: {},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
console.log(this.page_content);
for (let index in this.page_content) {
switch (this.page_content[index].type) {
// if type is banner
case 'banner':
switch (this.page_content[index].display) {
// if display is carousel
case 'carousel':
console.log('carousel')
// end if display is carousel
this.widgets.push({
'type': 'Carousel',
'images': this.page_content[index].items,
})
}
// end if type is banner
}
}
});
}
}
}
</script>
and this is Carousel.vue which i need to be imported when needed with passing props :
<template>
<div>
<div >
<VueSlickCarousel>
<div v-for="image in images">
<img src="{{img}}">
</div>
</VueSlickCarousel>
</div>
</div>
</template>
<script>
import VueSlickCarousel from 'vue-slick-carousel'
import 'vue-slick-carousel/dist/vue-slick-carousel.css'
import 'vue-slick-carousel/dist/vue-slick-carousel-theme.css'
export default
{
components: {VueSlickCarousel},
name:'Carousel',
props:[
'images'
],
methods:
{
}
}
</script>
how to add Carousel.vue component to Home.vue dynamically some thing like:
if(data.display == 'carousel')
{
<carousel images="data.images"></carousel>
}
Import the component to your Home.vue :
import Carousel from './Carousel.vue'
export default {
components: {Carousel},
}
Then loop in your template:
<carousel v-for="(widget,index) in widgets" :key="index" :images="widget.images"/>
Best to use a widget.id rather than index for the key prop
This is the correct answer !
<template>
<div>
<template v-for="widget in widgets">
<div v-if="widget.type == 'carousel'" :key="widget.type">
<carousel
:images="widget.images"
:arrows ="widget.arrows"
:dots = "widget.dots"
>
</carousel>
</div>
</template>
</div>
</template>
<script>
import Carousel from './widgets/Carousel.vue'
export default {
components: {Carousel},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
console.log(this.page_content);
for (let index in this.page_content) {
switch (this.page_content[index].type) {
// if type is banner
case 'banner':
switch (this.page_content[index].display) {
// if display is carousel
case 'carousel':
console.log('carousel')
// end if display is carousel
this.widgets.push({
'type': 'carousel',
'arrows':true,
'dots':true,
'images': this.page_content[index].items,
})
}
// end if type is banner
}
}
});
}
}
}
</script>

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