VueJS v3, Vuex and Composition API and v-model on input field - vue.js

Problem
I have some code that is getting a search query from a Vuex store. I am using a computed property to get the search query, and then binding it to the v-model of the input field. I want to be able to edit/change the search term via the input field, and then submit the new search query, which will then perform a new search query.
But the since the computed property is "Read Only", when I change the search query in the input field, it does not update search query, and causes a warning:
vendor.js:16674 Write operation failed: computed value is readonly
Question
How can I get the search query from the Vuex, populate a input field, change/update it, and then submit the changed query? I have tried to find a computed setter for the composition API, but cannot find one.
Any ideas? or should I look at another approach?
Below is the code
<template>
<form role="search"
aria-label="Sitewide"
#submit.prevent="submitSearch"
autocomplete="off">
<input type="text" v-model="searchQuery" />
<button type="button" v-on:click="submitSearch">Search</button>
</form>
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
name: "ProductSearchBox",
setup() {
const store = useStore();
const searchQuery = computed(() => store.getters["search/getFiltersSearchTerm"]);
const submitSearch = () => {
store.dispatch('search/performSearch', searchQuery);
}
return {
searchQuery,
submitSearch
}
}
}
</script>

This sounds more like a use case for a watch.
const searchQuery = ref('');
watch(
() => store.getters["search/getFiltersSearchTerm"],
(term) => searchQuery.value = term
);

You can use computed property for v-model like this:
<template>
<form role="search"
aria-label="Sitewide"
#submit.prevent="submitSearch"
autocomplete="off">
<input type="text" v-model="searchQuery" />
</form>
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
name: "ProductSearchBox",
setup() {
const store = useStore();
const searchQuery = computed({
get: () => store.getters['search/getFiltersSearchTerm'],
set: (newValue) => store.commit('search/yourMutation', newValue)
});
const submitSearch = () => {
store.dispatch('search/performSearch', searchQuery);
}
return {
searchQuery,
submitSearch
}
}
}
</script>

Related

How save the value entered in input using Vuex?

I started learning Veux. Using the example of creating a card and saving its contents. I created input bound it using two-way binding v-bind - passed it to State. And then nothing is clear. Maybe there are detailed lessons or your examples and help.
<!-- Template
<input id="nameTask" type="text" class="form-control" placeholder="Input Label For New ToDo"
aria-describedby="basic-addon1" v-model="headPrivive">
--!>
<!-- Script
headPrivive () {
return this.$store.getters.headPrivive;
} --!>
<!--Store
import { createStore } from 'vuex'
const store = createStore({
state: {
headPrivive: " ",
},
--!>
Create a variable in data in the component. This will be passed to the store. Set this variable as v-model to the input and later handle setting it in store. Use mutations to update the store states.
<template>
<input
#input="headPrivive()"
id="nameTask"
type="text"
class="form-control"
placeholder="Input Label For New ToDo"
aria-describedby="basic-addon1"
v-model="value"
>
</template>
<script>
export default {
data() {
return {
value: ''
}
}
methods: {
headPrivive () {
// *commit* is the built-in vuex method to call mutations.
// The first parameter is the name of mutation
// The second parameter is the data *value* being passed to the mutation as *payload*
this.$store.commit('updateHeadPrivive', this.value)
}
}
}
</script>
import { createStore } from 'vuex'
const store = createStore({
state: {
headPrivive: " ",
},
mutations: {
// Payload here is the *value* passed from the method *headPrivive* in the component
updateHeadPrivive(state, payload) {
this.headPrivive = payload
}
}
})

Can't get v-model work with Composition API and Vuex

I've read several posts on stackoverflow and other websites, but still can't figure out what's going wrong in my case.
I'm building an app following composition api approach and using a variable called modelStartDate (which I initiate at Jan 3, 2022). This is how my store looks:
import { createStore } from 'vuex'
export default createStore({
state: {
modelStartDate: new Date(2022, 0, 3)
},
mutations: {
modelStartDateMutation(state, newDate) {
state.modelStartDate = newDate
}
},
actions: {
},
getters: {
},
modules: {
}
})
In the relevant Vue file, I have the following code snippet:
<template>
<nav class="left-bar">
<div class="block" id="modelStartDate">
<label>Model start date</label>
<input type="date" v-model="modelStartDateProxy" />
</div>
<p>{{ modelStartDate }}</p>
</nav>
</template>
<script>
import { ref } from '#vue/reactivity'
import { useStore } from 'vuex'
import { computed } from '#vue/runtime-core'
export default {
setup() {
const store = useStore()
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit("modelStartDateMutation", newDate)
})
const modelStartDate = store.state.modelStartDate
return { modelStartDateProxy, modelStartDate }
}
}
</script>
When I run the page, the paragraph tag prints the right date, however the input tag, where the user can change the date, is empty (I was expecting Jan 3, 2022 to be pre-selected). When the date is changed, nothing seems to change in the app. I'm getting no errors. Any idea what I'm doing incorrectly?
Also, can I access store's modelStartDate state without having to define it separately (redundantly?) in the vue setup() section?
First, I don't know which tutorial you read. But to me, the problem is here:
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit("modelStartDateMutation", newDate)
})
const modelStartDate = store.state.modelStartDate
The snippet
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit("modelStartDateMutation", newDate)
})
is weird to me.
Duplicate of store.state.modelStartDate. DRY.
<p>{{ modelStartDate }}</p> render data from const modelStartDate = store.state.modelStartDate. But the data was only assign once. So the new value was not render on input was changed.
Solution:
const modelStartDate = computed(() => store.state.modelStartDate);
You can take a look at this playground.
The html element input returns a string: "YYYY-MM-DD". Therefore you need the syntax new Date(value)
Take a look at this playground
<template>
<label>Model start date</label>
<input type="date" v-model="modelStartDateProxy" />
<p>{{ modelStartDateProxy }}</p>
</template>
<script>
import { store } from './store.js' //mock-up store
import { ref, computed } from 'vue'
export default {
setup() {
const modelStartDateProxy = computed({
get: () => store.state.modelStartDate,
set: (newDate) => store.commit(newDate) // Use real Vuex syntax
})
return { modelStartDateProxy }
}
}
</script>
//Mock-up Store (not real vuex)
import {reactive} from 'vue'
export const store = reactive({
state: {
modelStartDate: new Date(2022, 0, 3)
},
commit: (value) => store.state.modelStartDate = new Date(value) // new Date(value)
})

Making API call using Axios with the value from input, Vue.js 3

I am making an app using this API. The point I'm stuck with is calling the API. If I give the name of the country, the data of that country comes.
Like, res.data.Turkey.All
I want to get the value with input and bring the data of the country whose name is entered.
I am getting value with searchedCountry. But I can't use this value. My API call does not happen with the value I get. I'm getting Undefined feedback from Console.
Is there a way to make a call with the data received from the input?
<template>
<div>
<input
type="search"
v-model="searchedCountry"
placeholder="Search country"
/>
</div>
</template>
<script>
import axios from 'axios';
import { ref, onMounted} from 'vue';
export default {
setup() {
let data = ref([]);
const search = ref();
let searchedCountry = ref('');
onMounted(() => {
axios.get('https://covid-api.mmediagroup.fr/v1/cases').then((res) => {
data.value = res.data.Turkey.All;
});
});
return {
data,
search,
searchedCountry,
};
},
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I'm work with Vue.js 3
There are a few things wrong with your code:
Your axios call is only called once, when the component mounts (side note here, if you really want to do something like that, you can do it directly within the setup method)
You don't pass the value from searchedCountry to the axios API
Use const for refs
I'd use a watch on the searchedCountry; something like this (I don't know the API contract):
<template>
<div>
<input
type="search"
v-model="searchedCountry"
placeholder="Search country"
/>
</div>
</template>
<script>
import axios from 'axios';
import { ref, watch } from 'vue';
export default {
setup() {
const searchedCountry = ref('');
const data = ref([]);
watch(
() => searchedCountry,
(country) => axios.get(`https://covid-api.mmediagroup.fr/v1/cases/${country}`).then((res) => data.value = res.data.Turkey.All);
);
return {
data,
searchedCountry,
};
},
};
</script>

How to use v-model in Vue with Vuex and composition API?

<template>
<div>
<h1>{{ counter }}</h1>
<input type="text" v-model="counter" />
</div>
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const counter = computed(() => store.state.counter)
return { counter }
},
}
</script>
How to change value of counter in the store when input value changes
I am getting this error in the console:
[ operation failed: computed value is readonly ]
Try this:
...
const counter = computed({
get: () => store.state.counter,
set: (val) => store.commit('COUNTER_MUTATION', val)
})
...
https://v3.vuejs.org/api/computed-watch-api.html#computed
Try this:
<input v-model="counter">
computed: {
counter: {
get () {
return this.$store.state.a
},
set (value) {
this.$store.commit('updateA', value)
}
}
}
With composition API
When creating computed properties we can have two types, the readonly and a writable one.
To allow v-model to update a variable value we need a writable computed ref.
Example of a readonly computed ref:
const
n = ref(0),
count = computed(() => n.value);
console.log(count.value) // 0
count.value = 2 // error
Example of a writable computed ref:
const n = ref(0)
const count = computed({
get: () => n.value,
set: (val) => n.value = val
})
count.value = 2
console.log(count.value) // 2
So.. in summary, to use v-model with Vuex we need to use a writable computed ref. With composition API it would look like below:
note: I changed the counter variable with about so that the code makes more sense
<script setup>
import {computed} from 'vue'
import {useStore} from 'vuex'
const store = useStore()
const about = computed({
get: () => store.state.about,
set: (text) => store.dispatch('setAbout', text)
})
</script>
<template>
<div>
<h1>{{ about }}</h1>
<input type="text" v-model="about" />
</div>
</template>

How I filter data from an input using vue/vuex?

I would like to know how through an input how I can filter data that comes from a computed property.
My problem comes from property computed dataFiltered () where a getter is returned but it has a string as parameter.
I would like that through an input when writing a word the information is filtered.
Home.vue
</template>
<input type="text" :v-model="search" class="form-control" id="search" placeholder="Search..."/>
<div>
<ul v-for="item in dataFiltered" :key="item.id">
<li>{{item}}</li>
</ul>
</div>
</template>
<script>
import {ACTYPE} from '../store/types/actions_types';
import {mapState , mapGetters} from 'vuex';
export default {
name: 'home',
created(){
this.$store.dispatch(ACTYPE.GET_MOVIES);
},
data(){
return{
search: ''
}
},
computed: {
...mapState(['topMovies' , 'loading']),
...mapGetters(['filterData']),
dataFiltered(){
// parameter harcored
return this.filterData("Sp")
}
},
}
</script>
store/getters.js
export const getters = {
TopMovies: (state) =>{
return state.topMovies.map(data =>{
return data
});
},
filterData: (state) => (search) =>{
let query = new RegExp(search , 'i');
console.log(query);
//return state.topMovies.filter(data => data.name === query);
return state.topMovies.filter(data =>{
return data.name.toLowerCase().includes(search.toLowerCase())
})
},
};
First of all, your :v-model="search" should just be v-model="search". :v-model is binding the value to something.
Secondly, I do not see you passing the search in the component to the Vuex store which is then applied in the getters.
This is not tested and I do not think you require the store for this to work. The most you may require the store is to fetch the initial data using the mapGetters helper and assuming topMovies in this case.
In your computed property, all you need to do is to filter the data using the search query string.
E.g.
computed() {
...mapGetters(["topMovies"]),
filteredData() {
return this.topMovies.filter(movie => movie.name.toLowerCase().includes(this.search.toLowerCase()))
}
}