How To Correctly Bind Form in Vue & Vuex - Difference of v-model, :value & :value.sync - vue.js

I'm fairly new to Vue & Vuex and am unsure on how to set up my form correctly.
<template>
<div>
<div v-if="error">{{error.message}}</div>
<form #submit.prevent>
<h1>Register</h1>
<input type="email" name="" id="" :value.sync="email" placeholder="Email">
<input type="password" :value.sync="password" placeholder="Password">
<button #click="signup">Submit</button>
Sign in
</form>
</div>
</template>
<script>
import {fireAuth} from '~/plugins/firebase.js'
export default {
data: () => ({
email: '',
password: '',
error: ''
}),
methods: {
signup() {
fireAuth.createUserWithEmailAndPassword(this.email, this.password).then(res => {
if (res) {
this.$store.commit('setCurrentUser', res.user)
this.$router.push('/')
}
}).catch(err => {
this.error = err
console.log(err)
})
}
}
}
</script>
At first I tried v-model to bind the form, it was throwing and error as I was mutating the store but I'm not storing email and password so I'm unsure how this is mutating the store? If I use :value or :value.sync then the email and password fields remain an empty string, so I'm not sure how to set up these correctly or how they differ from v-model
Edit: Adding my code from the store as requested
export const state = () => ({
currentUser: {}
})
export const mutations = {
setCurrentUser (state, obj) {
state.currentUser = obj
}
}

Vue.config.devtools = false
Vue.config.productionTip = false
const vm = new Vue({
el: "#app",
data() {
return {
email: '',
password: ''
}
},
methods: {
signup() {
console.log(this.email) // email value
console.log(this.password) // password value
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<div>
<form #submit.prevent="signup">
<h1>Register</h1>
<input type="email" v-model="email" placeholder="Email">
<input type="password" v-model="password" placeholder="Password">
<input type="submit">
</form>
</div>
</div>
</body>
</html>

I think I have found the solution, it was the way in which I was trying to use firebase. I have used v-model, but am using a firebase method onAuthStateChanged, only if that is called will I change the store. So in my firebase.js I added
const fireAuth = firebase.auth()
const fireDb = firebase.database()
export { fireAuth, fireDb }
export default async ({ store }) => {
await asyncOnAuthStateChanged(store)
}
function asyncOnAuthStateChanged (store) {
return new Promise(resolve => {
fireAuth.onAuthStateChanged(user => {
resolve(user)
store.commit('setCurrentUser', JSON.parse(JSON.stringify(user)))
})
})
}

Related

how can I get the data from localStorage to show in homePosts vue.js compositions API

I'm doing a simple blog app to practice vue.js. I'm using composition API. I have stored data that get filled in in a form. This data I want to print out in another component homePosts where you can see the written blogpost with writer, headline and blogtext. I have used v-model, stored data to localStorage, in homePosts I have used v-for and {{ }} syntax to get data. But nothing shows in homePosts.
Can someone please see what im missing.
writePost.vue
<template>
<div>
<form class="form">
<label for="writer">Writer name: </label>
<input v-model="newWriter" type="text" max="500" />
<br />
<label for="img">Select image:</label>
<input type="file" id="img" name="img" accept="image/*" />
<br />
<label for="headline">Headline </label>
<input v-model="newHeadline" type="text" max="500" />
<label>Your blogtext: </label>
<textarea v-model="newNote" name="" id="" cols="30" rows="30"></textarea>
<button type="submit" #click="addNote" class="button"><router-link to="/homePosts" class="link">Post blog</router-link></button>
</form>
</div>
</template>
<script setup>
import { ref } from "vue";
const newNote = ref("");
const newWriter = ref("");
const newHeadline = ref("");
const notes = ref([]);
const addNote = () => {
notes.value.push({
id: Math.floor(Math.random() * 1000000),
text: newNote.value,
writer: newWriter.value,
headline: newHeadline.value,
});
addLocalStorage(notes)
};
const addLocalStorage = (notes) => {
localStorage.setItem("notes", JSON.stringify(notes))
JSON.parse(localStorage.getItem("notes"));
}
</script>
homePosts.vue
<template>
<div class="post-container">
<h1>Blog Posts</h1>
<div class="post-mini-container" >
<div class="post" v-for="note in notes" :key="note.id">
<!-- <img class="img-post" src="#/assets/person1.jpg"> -->
<p class="writer"> {{ note.writer }}</p>
<p class="headline"> {{ note.headline }}</p>
<p class="blog-text" > {{ note.text }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'homePosts'
}
</script>
You need to start your ref already parsing the existing items in your localStorage.
const notes = ref(JSON.parse(localStorage.getItem('notes') ?? '[]');
Or better yet, use a computed getter/setter:
const notes = computed({
get: () => JSON.parse(localStorage.getItem('notes') ?? '[]'),
set: (value) => {
localStorage.setItem('notes', JSON.stringify(value))
}
});
Or even better, take a look at vueUse/useLocalStorage 🎉
there are two approaches that you can follow, "event bus" or "pinia / vuex".
i'll explain how you can implement event bus
(you can check this post for inspiration: https://medium.com/#certosinolab/using-event-bus-in-vue-js-3-425aae8c21a6)
Add global event bus
install mit: npm install --save mitt
go to your main.ts / main.js and add the global property
import mitt from 'mitt';
const dispatcher = mitt();
const app = createApp(App);
app.config.globalProperties.dispatcher = dispatcher;
app.mount('#app');
update "script" content in writePost.vue component
<script setup>
import { ref , getCurrentInstance } from "vue";
const app = getCurrentInstance();
const dispatcher= app?.appContext.config.globalProperties.dispatcher;
const newNote = ref("");
const newWriter = ref("");
const newHeadline = ref("");
const notes = ref([]);
const addNote = () => {
notes.value.push({
id: Math.floor(Math.random() * 1000000),
text: newNote.value,
writer: newWriter.value,
headline: newHeadline.value,
});
// emit notes
dispatcher.emit("updateNotes" , notes);
addLocalStorage(notes)
};
const addLocalStorage = (notes) => {
localStorage.setItem("notes", JSON.stringify(notes))
JSON.parse(localStorage.getItem("notes"));
}
</script>
update "script" content in homePosts.vue component
<script>
export default {
name: 'homePosts',
data() {
return {notes: []}
},
mounted() {
this.notes = JSON.parse(localStorage.getItem("notes") ?? "[]");
this.dispatcher.on("updateNotes" , (notes) => {
this.notes = notes ?? [];
})
},
beforeDestroy() {
this.dispatcher.off("updateNotes");
},
}
</script>

How to send different requests according to checkbox in Vue?

I've got a checkbox with filtering, e.g.:
if false => display all data (one endpoint in API)
if true => display only filtered data (another endpoint)
I struggle with sending different requests like this
<template>
<div>
<div style="display: flex;
justify-content: center;">
<div class="col-md-9">
<b-form-checkbox v-model="my__specialization"
value=true
unchecked-value=false
#change="filterOrdersSpec()"
>Show filtered</b-form-checkbox>
<p>{{ my__specialization }}</p>
</div>
</div>
<div v-for="el in APIData" :key="el.id" >
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Orders',
data () {
return {
APIData: [],
my__specialization: false,
}
},
methods: {
filterOrdersSpec() {
if (this.my__specialization) {
axios
.get('/api/v1/filter-orders/specs')
.then(response => {
this.APIData = response.data.results
})
}
else {
axios
.get('/api/v1/orders/get')
.then(response => {
this.APIData = response.data.results
})
}
},
},
created () {
axios
.get('/api/v1/orders/get')
.then(response => {
this.APIData = response.data.results
})
.catch(err => {
console.log(err)
})
},
}
</script>
This construction does filter Data on setting checkbox to TRUE, but when setting FALSE, the request to filter is sent again. The question is - why and how to fix it?
Root cause of the problem : this.my__specialization returning boolean as a string "true"/"false". Hence, if (this.my__specialization) { ... } condition will always return true as it consider the value as string instead of boolean.
Solution : You have to convert the string value into a boolean.
if (this.my__specialization === "true") {
}
Live Demo :
new Vue({
el: '#app',
data() {
return {
APIData: null,
my__specialization: false
}
},
methods: {
filterOrdersSpec() {
this.APIData = (this.my__specialization === "true") ? 'Endpoint 1' : 'Endpoint 2';
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css"/>
<div id="app">
<b-form-checkbox
v-model="my__specialization"
value="true"
unchecked-value="false"
#input="filterOrdersSpec()"
>Show filtered</b-form-checkbox>
<div>my__specialization: <strong>{{ my__specialization }}</strong></div>
<div>APIData: <strong>{{ APIData }}</strong></div>
</div>

Get newest data after submit data without page reload using vue script setup + axios

Please kindly help me with this, because I'm new in vue.js.
So, I have 2 Vue files :
Greetinglist.vue, It calls data from api using axios.get
Greeting.vue, It posts data using axios.post
After submit data, how to refresh the data without reload the page ?
I'm using <script setup> tag.
(greetinglist.vue)
<div class="p-2" v-for="(message, index) in messages" :key="index">
<div>
<h3>{{ message.name }}<span>{{message.date}}</span></h3>
<p class="text-slate-300 text-lg">{{ message.greeting }}</p>
</div>
<hr/>
</div>
<script setup>
import {ref, onMounted } from 'vue'
import axios from 'axios'
let messages = ref([]);
function getMessages() {
axios.get('http://127.0.0.1:8000/api/messages')
.then((result) => {
messages.value = result.data
}).catch((err) => {
console.log(err.response.data)
})
}
onMounted(() => {
getMessages()
});
</script>
(Greeting.vue)
<form #submit.prevent="store()">
<div class="">
<input type="text" placeholder="" v-model="messages.name">
<textarea name="" id="" cols="28" rows="10" placeholder="" v-model="messages.greeting"></textarea>
</div>
<button>Submit</button>
</form>
<div>
<GreetingList/>
</div>
<script setup>
import { reactive } from 'vue'
import axios from 'axios'
import GreetingList from './GreetingList.vue'
const messages = reactive({
name: '',
greeting: '',
});
function store() {
axios.post('http://127.0.0.1:8000/api/messages', messages)
.then((result) => {
messages.name = ''
messages.greeting = ''
})
.catch((err) => {
})
}
</script>
You can move getMessages function to greeting.vue, and change greetinglist.vue to use props
greeting.vue
<form #submit.prevent="store()">
<div class="">
<input type="text" placeholder="" v-model="messages.name">
<textarea name="" id="" cols="28" rows="10" placeholder="" v-model="messages.greeting"></textarea>
</div>
<button>Submit</button>
</form>
<div>
<GreetingList :messages="messageList" />
</div>
<script setup>
import { reactive } from 'vue'
import axios from 'axios'
import GreetingList from './GreetingList.vue'
const messages = reactive({
name: '',
greeting: '',
});
let messageList = ref([])
function store() {
axios.post('http://127.0.0.1:8000/api/messages', messages)
.then((result) => {
messages.name = ''
messages.greeting = ''
getMessages()
})
.catch((err) => {
})
}
function getMessages() {
axios.get('http://127.0.0.1:8000/api/messages')
.then((result) => {
messageList.value = result.data
}).catch((err) => {
console.log(err.response.data)
})
}
onMounted(() => {
getMessages()
});
</script>
greetinglist.vue
<script setup>
defineProps(['messages'])
</script>
If I understood your requirement correctly, I am assuming you have both the components in a single page and you want to get the details of newly added greeting from Greeting.vue in the GreetingList.vue reactively (without refreshing the route). If Yes, You can achieve that by calling a getMessages() method on successfully promise of axios.post and then pass the results in the GreetingList.vue.
Demo (I just created it using Vue 2, You can change it accordingly as per Vue 3) :
Vue.component('child', {
// declare the props
props: ['msglist'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: `<div>
<div v-for="(message, index) in msglist" :key="index">
<h3>{{ message.name }}</h3>
<p>{{ message.greeting }}</p>
<hr/>
</div>
</div>`
});
var app = new Vue({
el: '#app',
data: {
messages: {
name: null,
greeting: null
},
// For demo, I am just mock data for initial greeting listing.
messageList: [{
name: 'Alpha',
greeting: 'Hi !'
}, {
name: 'Beta',
greeting: 'Hello !'
}]
},
methods: {
store() {
// Post API call will happen here.
// on success, make a call to get list of greetings.
if (this.messages.name && this.messages.greeting) {
this.getMessages();
}
},
getMessages() {
// Get API call will happen here. For demo, I am just using the mock data and pushing the newly submitted greeting in a exisiting messageList.
this.messageList.push({
name: this.messages.name,
greeting: this.messages.greeting
})
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" placeholder="Name" v-model="messages.name"/>
<textarea cols="28" rows="10" placeholder="Greeting" v-model="messages.greeting"></textarea>
<button #click="store">Submit</button>
<child :msglist="messageList">
</child>
</div>
<template>
<div>
<h1>Post component</h1>
<form #submit="submitData" method="post">
<input type="text" placeholder="Enter Title" name="tilte" v-model="userdata.title" /><br />
<br />
<input type="text" placeholder="Enter Author" name="author" v-model="userdata.author" /><br /><br />
<button type="submit">Submit Data</button>
</form>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "postcomponent",
data() {
return {
userdata: {
title: null,
author: null,
},
};
},
methods: {
submitData(e) {
axios.post(
"http://localhost:3000/posts",
this.userdata
).then((result) => {
console.warn(result)
});
e.preventDefault();
},
},
};
</script>

Watch, Compare & post updated form data to API using Axios in Vue 3

I need help to complete my code.
This is what have done.
I am fetching options from API, so I have defined the initial state as
empty.
Once I have a response from API, I update the state of options.
My form is displayed once I have a response from API.
Now using v-bind I am binding the form.
Where I need help.
I need to watch for the changes in form. If the values of form elements are different from the state of the API response, I would like to enable the submit button.
When the save button is clicked, I need to filter the options that were changed & submit that form data to my pinia action called updateOptions.
Note: API handles post data in this way. Example: enable_quick_view: true
Thank you in advance.
options.js pinia store
import { defineStore } from 'pinia'
import Axios from 'axios';
import axios from 'axios';
const BASE_API_URL = adfy_wp_locolizer.api_url;
export const useOptionsStore = defineStore({
id: 'Options',
state: () => ({
allData: {},
options: {
enable_quick_view: null, // boolean
quick_view_btn_label: "", // string
quick_view_btn_position: "", // string
},
newOptions: {}, // If required, holds the new options to be saved.
message: "", // Holds the message to be displayed to the user.
isLoading: true,
isSaving: false,
needSave: false,
errors: [],
}),
getters: {
// ⚡️ Return state of the options.
loading: (state) => {
return state.isLoading;
},
},
actions: {
// ⚡️ Use Axios to get options from api.
fetchOptions() {
Axios.get(BASE_API_URL + 'get_options')
.then(res => {
this.alldata = res.data.settings;
let settings = res.data.settings_values;
/*
* Set options state.
*/
this.options.enable_quick_view = JSON.parse(
settings.enable_quick_view
);
this.options.quick_view_btn_label =
settings.quick_view_btn_label;
this.options.quick_view_btn_position = settings.quick_view_btn_position;
/*
* End!
*/
this.isLoading = false;
})
.catch(err => {
this.errors = err;
console.log(err);
})
.finally(() => {
// Do nothing for now.
});
},
// ⚡️ Update options using Axios.
updateOptions() {
this.isSaving = true;
axios.post(BASE_API_URL + 'update_options', payload)
.then(res => {
this.needSave = false;
this.isSaving = false;
this.message = "Options saved successfully!";
})
.catch(err => {
this.errors = err;
console.log(err);
this.message = "Error saving options!";
})
}
},
});
Option.vue component
<script setup>
import { onMounted, watch } from "vue";
import { storeToRefs } from "pinia";
import { Check, Close } from "#element-plus/icons-vue";
import Loading from "../Loading.vue";
import { useOptionsStore } from "../../stores/options";
let store = useOptionsStore();
let { needSave, loading, options, newOptions } = storeToRefs(store);
watch(
options,
(state) => {
console.log(state);
// Assign the option to the newOptions.
},
{ deep: true, immediate: false }
);
onMounted(() => {
store.fetchOptions();
});
</script>
<template>
<Loading v-if="loading" />
<form
v-else
id="ui-settings-form"
class="ui-form"
#submit="store.updateOptions()"
>
<h3 class="option-box-title">General</h3>
<div class="ui-options">
<div class="ui-option-columns option-box">
<div class="ui-col left">
<div class="label">
<p class="option-label">Enable quick view</p>
<p class="option-description">
Once enabled, it will be visible in product catalog.
</p>
</div>
</div>
<div class="ui-col right">
<div class="input">
<el-switch
v-model="options.enable_quick_view"
size="large"
inline-prompt
:active-icon="Check"
:inactive-icon="Close"
/>
</div>
</div>
</div>
</div>
<!-- // ui-options -->
<div class="ui-options">
<div class="ui-option-columns option-box">
<div class="ui-col left">
<div class="label">
<p class="option-label">Button label</p>
</div>
</div>
<div class="ui-col right">
<div class="input">
<el-input
v-model="options.quick_view_btn_label"
size="large"
placeholder="Quick view"
/>
</div>
</div>
</div>
</div>
<!-- // ui-options -->
<button type="submit" class="ui-button" :disabled="needSave == true">
Save
</button>
</form>
</template>
<style lang="css" scoped>
.el-checkbox {
--el-checkbox-font-weight: normal;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
</style>
In the watch function you can compare the new and old values. But you shuld change it to:
watch(options, (newValue, oldValue) => {
console.log(oldValue, newValue);
// compare objects
}, {deep: true, immediate: false};
Now you can compare the old with the new object. I think search on google can help you with that.
Hope this helps.

Vuex store Getter loadings faster than store State

I have a nuxtJS vue2 components as follows:
<template>
<div class="product-fullpage">
<div class="product-card">
<img :src="productById(this.$route.params.id).imageURL">
<div class="product-headings">
<div class="product-info" v-animate-on-scroll>
<h1>{{ productById(this.$route.params.id).name }}</h1>
<h2>£{{ productById(this.$route.params.id).price }}</h2>
</div>
<div class="product-cart" v-animate-on-scroll>
<div class="quantity-info">
<label for="quantity">Quantity:</label>
<input v-model.number="productInfo.quantity" name="quantity" type="number" min="0" max="99"/>
</div>
<button #click="addToCart" :disabled="noQuantity">Add To Cart ></button>
</div>
</div>
</div>
</div>
</template>
I'm using a getter that does the following:
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['productById'])
},
}
</script>
Here's the getter
export const getters = {
productById: (state) => (id) => {
return state.products.find(product => product.id === id)
},
}
my state is set to pull from firebase
export const actions = {
async setProducts({commit}) {
let colRef = collection(db, 'products')
const querySnapshot = await getDocs(colRef)
querySnapshot.forEach((doc) => {
const imageDownloadURL = getDownloadURL(ref(storage, `${doc.data().imageRef}`))
.then( url => {
// console.log(url)
let article = ({
id: doc.id,
name: doc.data().name,
price: doc.data().price,
description: doc.data().description,
imageURL: url
})
commit('setProducts', article)
})
})
},
}
the mutation to set the state:
export const mutations = {
setProducts(state, article) {
let matchProduct = state.products.find(product => product.id == article.id)
if(!matchProduct) {
state.products.push(article)
}
},
}
and this is my state:
export const state = () => ({
products: [],
})
i thought that if i load everything beforehand in default.vue under 'layouts' that i can then have the store.state.products set.
<template>
<div class="container">
<!-- <nuxt /> -->
<nuxt v-if="!loading"/>
<div class="overlay" v-else>
Loading...
</div>
</div>
</template>
<script>
export default {
created() {
this.loading = true
this.$store.dispatch('setCart')
this.$store.dispatch('setProducts')
.finally(() => (this.loading=false))
},
data() {
return {
loading: false,
}
},
}
</script>
sorry if this is turning out to be a long post - but basically on initial load, I get my imageURL, name and price. but then on reload it comes out empty. i believe the getter is occurring before the store is loaded. how do i set it so that i can state.products.find(product => product.id == article.id) for my getter after state is loaded?