Default props value are not selected in vue3 options api - vue.js

I created a select2 wrapper in vue3 with options API everything working fine but the problem is that when getting values from calling API it's not selected the default value in the select2 option. but when I created a static array of objects it does. I don't know why it's working when it comes from the API
Parent Component
Here you can I passed the static options array in options props and my selected value is 2 and it's selected in my Select2 component, but when passed formattedCompanies it's not which is the same format as the static options array then why is not selected any reason here..?
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
:options="options"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : [],
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
Select2 Component
Here is what my select2 component look like, did I do anything wrong here, please anybody help me
<template>
<select class="form-control">
<slot/>
</select>
</template>
<script>
export default {
name: "Select2",
props: {
options: {
type: [Array, Object],
required: true
},
modelValue: [String, Number],
placeholder: {
type: String,
default: "Search"
},
allowClear: {
type: Boolean,
default: true
},
},
mounted() {
const vm = this;
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
},
watch: {
modelValue(value) { // update value
$(this.$el)
.val(value)
.trigger("change");
},
options(options) { // update options
$(this.$el)
.empty()
.select2({data: options});
},
},
destroyed() {
$(this.$el)
.off()
.select2("destroy");
}
}
</script>

Probably when this Select2 mounted there is no companies. It is empty array after that it will make API call and it it populates options field and clear all options.
Make:
companies : null,
Change it to
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
It should be like this:
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : null,
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>

The problem was that my parent component and Select2 component mounted at the same time that's why my computed value is not initialized so the selected value is not selected in the option,
problem solved by setTimeOut function in mounted like this
Select2 Component
<script>
mounted() {
const vm = this;
setTimeout(() => {
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
}, 500)
},
</script>

Related

How do I check if the Multi-select component in Vue js is empty?

I am trying to check if the Multi-select component is empty. But upon checking it kept telling me it's not null. Do I need to use a v-model for this?
Here's the code for the Multi-select component:
<template>
<div>
<Multiselect
v-model="value"
mode="tags"
:close-on-select="false"
:searchable="false"
:create-option="true"
:options="multi_options"
class="multiselect-orange multiselect"
/>
</div>
</template>
<script>
import Multiselect from "#vueform/multiselect";
export default {
components: {
Multiselect,
},
props: {
multi_options: {
type: Array,
required: true,
},
inputSelected:{
required:true,
},
method: { type: Function },
// default: {
// type: String,
// required: false,
// default: null,
// },
},
data() {
return {
value: this.inputSelected,
};
},
mounted() {
this.$emit("set-input", this.value);
},
watch: {
value: function () {
this.$emit("set-input", this.value);
},
},
};
</script>
Here's the code where I used the component:
<div class="inputHolder">
<label class="body"> Skills:</label>
<MultiSelect :multi_options="this.skills" #set-input="setSkillSet" :inputSelected="staffSkills"/>
</div>
Here's the code when I tried to check if the Multi-select is empty:
checkForm(){
this.errors = []
if (this.staffSkills) {
return true;
}
if(this.staffSkills === []){
this.errors.push('Select at least one.');
}
if(this.errors.length){
return this.errors
}
}
},

Vue.js: Child Component mutates state, parent displays property as undefined

I have a parent component that lists all the tasks:
<template>
<div class="tasks-wrapper">
<div class="tasks-header">
<h4>{{ $t('client.taskListingTitle') }}</h4>
<b-button variant="custom" #click="showAddTaskModal">{{ $t('client.addTask') }}</b-button>
</div>
<b-table
striped
hover
:items="tasks"
:fields="fields"
show-empty
:empty-text="$t('common.noResultsFound')">
</b-table>
<AddTaskModal />
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import AddTaskModal from '#/components/modals/AddTaskModal'
import moment from 'moment'
export default {
name: 'TaskListing',
components: {
AddTaskModal
},
data () {
return {
tasks: [],
fields: [
{ key: 'createdOn', label: this.$t('tasks.tableFields.date'), formatter: 'formatDate' },
{ key: 'domain', label: this.$t('tasks.tableFields.task') },
{ key: 'comment', label: this.$t('tasks.tableFields.comment') },
{ key: 'status', label: this.$t('tasks.tableFields.status') }
]
}
},
computed: {
...mapGetters('users', ['user'])
},
methods: {
...mapActions('tasks', ['fetchTasks']),
...mapActions('users', ['fetchUserById']),
formatDate: function (date) {
return moment.utc(date).local().format('DD.MM.YYYY HH:mm')
},
showAddTaskModal () {
this.$bvModal.show('addTaskModal')
}
},
async mounted () {
const currUserId = this.$router.history.current.params.id
if (this.user || this.user.userId !== currUserId) {
await this.fetchUserById(currUserId)
}
if (this.user.clientNumber !== null) {
const filters = { clientReferenceNumber: { value: this.user.clientNumber } }
this.tasks = await this.fetchTasks({ filters })
}
}
}
</script>
Inside this component there is a child which adds a task modal.
<template>
<b-modal
id="addTaskModal"
:title="$t('modals.addTask.title')"
hide-footer
#show="resetModal"
#hidden="resetModal"
>
<form ref="form" #submit.stop.prevent="handleSubmit">
<b-form-group
:invalid-feedback="$t('modals.requiredFields')">
<b-form-select
id="task-type-select"
:options="taskTypesOptions"
:state="taskTypeState"
v-model="taskType"
required
></b-form-select>
<b-form-textarea
id="add-task-input"
:placeholder="$t('modals.enterComment')"
rows="3"
max-rows="6"
v-model="comment"
:state="commentState"
required />
</b-form-group>
<b-button-group class="float-right">
<b-button variant="danger" #click="$bvModal.hide('addTaskModal')">{{ $t('common.cancel') }}</b-button>
<b-button #click="addTask">{{ $t('modals.addTask.sendMail') }}</b-button>
</b-button-group>
</form>
</b-modal>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'AddTaskModal',
data () {
return {
comment: '',
commentState: null,
taskTypesOptions: [
{ value: null, text: this.$t('modals.addTask.taskType') },
{ value: 'OnBoarding', text: 'Onboarding' },
{ value: 'Accounts', text: 'Accounts' },
{ value: 'TopUp', text: 'Topup' },
{ value: 'Overdraft', text: 'Overdraft' },
{ value: 'Aml', text: 'Aml' },
{ value: 'Transfers', text: 'Transfers' },
{ value: 'Consultation', text: 'Consultation' },
{ value: 'TechnicalSupport', text: 'TechnicalSupport' },
{ value: 'UnblockPin', text: 'UnblockPin' },
{ value: 'Other', text: 'Other' }
],
taskType: null,
taskTypeState: null
}
},
computed: {
...mapGetters('users', ['user']),
...mapGetters('tasks', ['tasks'])
},
methods: {
...mapActions('tasks', ['addNewTask', 'fetchTasks']),
...mapActions('users', ['fetchUserById']),
async addTask (bvModalEvt) {
bvModalEvt.preventDefault()
if (!this.checkFormValidity()) { return }
const currUserId = this.$router.history.current.params.id
if (this.user || this.user.userId !== currUserId) {
await this.fetchUserById(currUserId)
}
const data = {
clientPhone: this.user.phoneNumber,
comment: this.comment,
clientReferenceNumber: this.user.clientNumber,
domain: this.taskType
}
await this.addNewTask(data)
if (this.user.clientNumber !== null) {
const filters = { clientReferenceNumber: { value: this.user.clientNumber } }
this.tasks = await this.fetchTasks({ filters })
// this.tasks may be useless here
}
console.log(this.tasks)
this.$nextTick(() => { this.$bvModal.hide('addTaskModal') })
},
checkFormValidity () {
const valid = this.$refs.form.checkValidity()
this.commentState = valid
this.taskTypeState = valid
return valid
},
resetModal () {
this.comment = ''
this.commentState = null
this.taskTypeState = null
}
}
}
</script>
When I add a task I call getalltasks to mutate the store so all the tasks are added. Then I want to render them. They are rendered but the property createdOn on the last task is InvalidDate and when I console log it is undefined.
The reason I need to call gettasks again in the modal is that the response on adding a task does not return the property createdOn. I do not want to set it on the front-end, I want to get it from the database.
I logged the store and all the tasks are added to the store.
Why is my parent component not rendering this particular createdOn property?
If I refresh the page everything is rendering fine.
If you add anything into a list of items that are displayed by v-for, you have to set a unique key. Based on your explanation, I assume that your key is the index and when you add a new item, you mess with the current indexes. Keys must be unique and unmutateable. What you need to do is to create a unique id for each element.
{
id: Math.floor(Math.random() * 10000000)
}
When you create a new task, use the same code to generate a new id, and use id as key. If this doesn't help, share your d-table and related vuex code too.

How to use an object not an array for an autocomplete form

I am new to Vue JS and having a mind blank when I've been creating my first Auto complete comp with VueCLI.
Here is the working code with an array:
https://pastebin.com/a8AL8MkD
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.toLowerCase().startsWith(this.state.toLowerCase())
})
},
I am now trying to get it to work with JSON so I can use axios to get the data.
In the filterStates method I understand I need to get the name of the item and do the lowercase on that, but it keeps erroring out when I try this:
https://pastebin.com/HPYyr9QH
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
Vue is erroring this:
[Vue warn]: Error in v-on handler: "TypeError: state.toLowerCase is not a function"
Do I need to pass in a key or something to identify each record?
Let's take your second pastebin :
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data() {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
setState(state) {
this.state = state
this.modal = false
}
}
}
</script>
You are calling : this.state.name.toLowerCase().
But this.state returns '' initially. So this.state.name is undefined.
You should initialize this.state with an object :
data() {
return {
state: {
name: ''
}
...
EDIT 17/03/2020
Here is another working solution :
What I did :
state is a string again. so I check this.state.toLowerCase()
In the setState function, I just pass the name : this.state = state.name
And to fix another error I changed this line : :key="filteredState.id" because a key should not be an object
<template>
<div>
<div class="AboutUs">
<PageBanner>
<template slot="title">Search</template>
</PageBanner>
<div class="container-fluid tarms-conditions">
<div class="row">
<div class="container">
<input
id
v-model="state"
type="text"
name
autocomplete="off"
class="form-control z-10"
placeholder="Search for a state..."
#input="filterStates"
#focus="modal = true"
>
<div
v-if="filteredStates && modal"
class="results z-10"
>
<ul class="list">
<li
v-for="filteredState in filteredStates"
:key="filteredState.id"
#click="setState(filteredState)"
>
{{ filteredState }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data () {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates () {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.toLowerCase())
})
},
setState (state) {
this.state = state.name
this.modal = false
}
}
}
</script>

Why is the computed property of this component not reactive:

I have an object like this:
myObject: {
items: [{
title: '140',
isActive: true,
}, {
title: '7',
isActive: false
}, {
title: '10',
isActive: false
}]
}
Which I'm using like this:
<my-component :items="myObject.items"></my-component>
This is how the component looks like:
<template>
<div class="i-panel panel-container d-flex"
<div
v-for="item in prefs.items"
class="panel-item"
#click="onClick(item)">
<!-- some content -->
</div>
</div>
</template>
<script>
export default {
name: 'IPanel',
props: {
items: {
type: Array,
default () {
return []
}
}
},
computed: {
// code
prefs () {
return {
items: this.items
}
}
},
methods: {
onClick (item) {
this.prefs.items.forEach(item => {
if (JSON.stringify(item) === JSON.stringify(clickedItem)) {
item.isActive = true
}
})
}
}
}
</script>
When I click an item (and that item is the same as the clickedItem), it's supposed to become isActive true. It does. But I have to refresh the Vue devtools or re-render the page for the change to take effect.
Why isn't item.isActive = true reactive?
In the code you posted, you are using a clickedItem object that is not defined anywhere. I don't know if this is just in the process of writing your question or if it is your problem.
However, when using clickedItem the right way, it seems to work: https://jsfiddle.net/d5z93ygy/4/
HTML
<div id="app" class="i-panel panel-container d-flex">
<div
v-for="item in prefs.items"
class="panel-item"
#click="onClick(item)">
<!-- some content -->{{ item.isActive ? 'active' : 'inactive' }}
</div>
</div>
JS
new Vue({
el: "#app",
data: {
items: [{
title: '140',
isActive: true,
}, {
title: '7',
isActive: false
}, {
title: '10',
isActive: false
}]
},
computed: {
// code
prefs () {
return {
items: this.items
}
}
},
methods: {
onClick (clickedItem) {
this.prefs.items.forEach(item => {
if (JSON.stringify(item) === JSON.stringify(clickedItem)) {
item.isActive = true
}
})
}
}
})
Change
<div
v-for="item in prefs.items"
class="panel-item"
#click="onClick(item)">
<!-- some content -->
</div>
to
<div
v-for="(item, index) in prefs.items"
class="panel-item"
#click="onClick(index)">
<!-- some content -->
</div>
Then, in your change method, go like this:
onClick (index) {
Vue.set(this.items, index, true);
}
https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats

VueJs: Form handling with Vuex and inputs generated with an API

Here's an example of a component:
<script>
export default {
name: 'my-form',
computed: {
myModules() {
return this.$store.state.myModules;
}
}
</script>
<template>
<form>
<p v-for="module in myModules">
<input type="checkbox" :value="module.id" />
<label>module.name</label>
</p>
<button type="submit">Submit</button>
</form>
</template>
The associated store:
state: {
myModules: []
},
mutations: {
setModules(state, modules) {
state.myModules = modules;
}
},
actions: {
getModules({commit}) {
return axios.get('modules')
.then((response) => {
commit('setModules', response.data.modules);
});
}
}
And finally, an example of return of the API "getModules":
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
My question: what's the best way to change the "isActive" property of each module to "true" when I check the checkbox corresponding to the associated module, directly in the store?
I know that Vuex's documentation recommends to use "Two-way Computed Property" to manage the forms, but here I don't know the number of modules that the API can potentially return, and I don't know their name.
Thank you in advance!
This is a little bit wicked approach, but it works. You can create an accessor object for every item you access in a loop:
const store = new Vuex.Store({
mutations: {
setActive (state, {index, value}) {
state.modules[index].isActive = value
}
},
state: {
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
}
});
const app = new Vue({
el: '#target',
store,
methods: {
model (id) {
const store = this.$store;
// here i return an object with value property that is bound to
// specific module and - thanks to Vue - retains reactivity
return Object.defineProperty({}, 'value', {
get () {
return store.state.modules[id].isActive
},
set (value) {
store.commit('setActive', {index: id, value});
}
});
}
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.min.js"></script>
<div id="target">
<div v-for="(item, id) in $store.state.modules">
Module #{{ item.id }} state: {{ item.isActive }}
</div>
<div v-for="(item, id) in $store.state.modules">
<label>
Module #{{ item.id }}
<input type="checkbox" v-model="model(id).value"/>
</label>
</div>
</div>
This is still quite a messy approach, but at least you don't have to commit mutations directly in template. With a little help of Vue.set() you can use this approach even to overcome standard reactivity caveats.
I have an alternative solution for you. You could make a child component for the checkboxes to clean up the code a bit.
UPD: I just realised that everything that I and #etki proposed is an overkill. I left the old version of my code below in case you still want to take a look. Here is a new one:
const modules = [{
id: 1,
name: 'Module 1',
isActive: true,
},
{
id: 2,
name: 'Module 2',
isActive: false,
},
{
id: 3,
name: 'Module 3',
isActive: false,
},
];
const store = new Vuex.Store({
state: {
myModules: [],
},
mutations: {
SET_MODULES(state, modules) {
state.myModules = modules;
},
TOGGLE_MODULE(state, id) {
state.myModules.some((el) => {
if (el.id === id) {
el.isActive = !el.isActive;
return true;
}
})
}
},
actions: {
getModules({
commit
}) {
return new Promise((fulfill) => {
setTimeout(() => {
commit('SET_MODULES', modules);
fulfill(modules);
}, 500)
});
}
}
});
const app = new Vue({
el: "#app",
store,
data: {},
methods: {
toggle(id) {
console.log(id);
this.$store.commit('TOGGLE_MODULE', id);
}
},
computed: {
myModules() {
return this.$store.state.myModules;
},
output() {
return JSON.stringify(this.myModules, null, 2);
},
},
mounted() {
this.$store.dispatch('getModules').then(() => console.log(this.myModules));
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="app">
<form>
<div v-for="data in myModules">
<label :for="data.id">{{ data.name }}: {{data.isActive}}</label>
<input type="checkbox" :id="data.id" :name="'checkbox-' + data.id" :checked="data.isActive" #change="toggle(data.id)">
</div>
</form>
<h3>Vuex state:</h3>
<pre v-text="output"></pre>
</div>
As you can see above you could just call a function on input change and pass an id as a parameter to a method that fires vuex action.
The old version of my code.
A new one on jsfiddle