How to initiliaze data with props value without call the props in template? - vue.js

I have props structured like this.
props: {
propsChooseColor: {
type: String,
required: true
}
}
The props received as expected(checked with vue devtools).
And then I try to initialize data with the props value like the official suggestion.
chooseColor: this.propsChooseColor
The problem is :
If I call chooseColor and propsChooseColor in template, the
chooseColor have same value as the props.
But if I don't call propsChooseColor, the chooseColor value is "".
E.g Value of chooseColor will empty.
<template>
...
data color: {{ chooseColor }} // RETURN ""
...
</template>
E.g Value of chooseColor will same as propsChooseColor.
<template>
...
data color: {{ chooseColor }} // RETURN BLACK
props color: {{ propsChooseColor }} // RETURN BLACK
...
</template>
I try to debug it and found that beforeUpdate hook is called if the props called in template.
What is happening? How do I initialize data without call the props? Approach usinh Computed property might not suited because i need to modify the data.
Edit. I also fetching data in the root component beforeRouteEnter hook like this.
beforeRouteEnter (to, from, next) {
const _id = to.query._id
const size = to.query.size
const color = to.query.color
axios.get(`http://localhost:8081/api/product-details/${_id}`)
.then(response => {
next(vm => {
vm.id = _id
vm.chooseSize = size
vm.chooseColor = color
vm.product = JSON.parse(JSON.stringify(response.data))
axios.get(`http://localhost:8081/api/product/all-seller/${vm.product.product_fullName}`)
.then(response => {
vm.productAllSeller = JSON.parse(JSON.stringify(response.data))
vm.fetchdataComplete = true
})
})
})
}

Related

Vue 3 ref changes the DOM but reactive doesn't

The following code works and I can see the output as intended when use ref, but when using reactive, I see no changes in the DOM. If I console.log transaction, the data is there in both cases. Once transaction as a variable changes, should the changes not be reflected on the DOM in both cases?
I'm still trying to wrap my head around Vue 3's composition API and when to use ref and reactive. My understanding was that when dealing with objects, use reactive and use ref for primitive types.
Using ref it works:
<template>
{{ transaction }}
</template>
<script setup>
import { ref } from 'vue'
let transaction = ref({})
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction.value = res.data
})
.catch((e) => {
console.log(e)
})
}
getPayByLinkTransaction()
</script>
Using reactive it doesn't work:
<template>
{{ transaction }}
</template>
<script setup>
import { reactive } from 'vue'
let transaction = reactive({})
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction = { ...res.data }
})
.catch((e) => {
console.log(e)
})
}
getPayByLinkTransaction()
</script>
Oh, when you do transaction = { ...res.data } on the reactive object, you override it, like you would with any other variable reference.
What does work is assigning to the reactive object:
Object.assign(transaction, res.data)
Internally, the object is a Proxy which uses abstract getters and setters to trigger change events and map to the associated values. The setter can handle adding new properties.
A ref() on the other hand is not a Proxy, but it does the same thing with its .value getter and setter.
From what I understand, the idea of reactive() is not to make any individual object reactive, but rather to collect all your refs in one single reactive object (somewhat similar to the props object), while ref() is used for individual variables. In your case, that would mean to declare it as:
const refs = reactive({transaction: {}})
refs.transaction = { ...res.data }
The general recommendation seems to be to pick one and stick with it, and most people seem to prefer ref(). Ultimately it comes down to if you prefer the annoyance of having to write transaction.value in your script or always writing refs.transaction everywhere.
With transaction = { ...res.data } the variable transaction gets replaced with a new Object and loses reactivity.
You can omit it by changing the data sub-property directly or by using ref() instead of reactivity()
This works:
let transaction = ref({})
transaction.data = res.data;
Check the Reactivity in Depth and this great article on Medium Ref() vs Reactive() in Vue 3 to understand the details.
Playground
const { createApp, ref, reactive } = Vue;
const App = {
setup() {
const transaction1 = ref({});
let transaction2 = reactive({ data: {} });
const res = { data: { test: 'My Test Data'} };
const replace1 = () => {
transaction1.value = res.data;
}
const replace2 = () => {
transaction2.data = res.data;
}
const replace3 = () => {
transaction2.data = {};
}
return {transaction1, transaction2, replace1, replace2, replace3 }
}
}
const app = Vue.createApp(App);
app.mount('#app');
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app">
transaction1: {{ transaction1 }}
<button type="button" #click="replace1()">1</button>
<br/>
transaction2: {{ transaction2 }}
<button type="button" #click="replace2()">2</button>
<button type="button" #click="replace3()">3</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
Since reactive transaction is an object try to use Object.assign method as follows :
Object.assign(transaction, res.data)

How to get params from an event vuetify vue js

I am learning vuetify and vue js and I want to know how to get params when I click on my treeview :
example :
in the console chrome with vue extension I have :
vue event update:active
(this gives me the id of the element in my treeview)
And I want to compare the params in a function to do some actions when I click on an element of my treeview, how can I do that ?
<v-treeview
v-if="userExists"
hoverable
open-on-click
#update:active="openRoute"
:items="items"
activatable>
</v-treeview>
and the function I made
function openRoute () {
const routes = _.map(items, function (item) {
if (item.name === 'Structure') { // here I want to compare with the id
return item.routeName
}
})
const test = routes.toString()
context.root.$router.push({ name: routes })
}
Thank you for you help
You can simply add the parameter to your function. For example:
function openRoute (emitedValue) {
console.log(emitedValue);
const routes = _.map(items, function (item) {
if (item.name === 'Structure') { // here I want to compare with the id
return item.routeName
}
})
const test = routes.toString()
context.root.$router.push({ name: routes })
}

Vue3 Composition API - How to load default values from Ajax?

I have read everything I can find, but there is a confusing amount of variability between approaches. I want to use the "setup" form of the Vue3 composition API, which I believe is the recommended approach for future compatibility.
I have a form with elements like this:
<form #submit.prevent="update">
<div class="grid grid-cols-1 gap-6 mt-4 sm:grid-cols-2">
<div>
<label class="text-gray-700" for="accountID">ID</label>
<input disabled id="accountID" v-model="accountID"
class="bg-slate-100 cursor-not-allowed w-full mt-2 border-gray-200 rounded-md focus:border-indigo-600 focus:ring focus:ring-opacity-40 focus:ring-indigo-500"
type="text"
/>
</div>
I want to load the current values with Ajax. If the user submits the form then I want to save the changed fields with a PATCH request.
I cannot work out how to change the form value with the result of the Ajax request and still maintain the binding.
Vue3 blocks changing the props directly (which makes sense), so the code below does not work:
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import axios from "axios";
import { useUserStore } from "#/stores/userStore";
const userStore = useUserStore();
const props = defineProps({
accountID: String,
});
const emit = defineEmits(['update:accountID'])
const accountID = computed({
get() {
return props.accountID;
},
set (value) {
return emit('update:accountID')
},
})
onMounted(async () => {
let response = await axios.get("http://localhost:8010/accounts", { headers: { "Authorization": "Bearer " + userStore.jws } });
// This is a readonly variable and cannot be reassigned
props.accountID = response.data.ID;
});
function update() {
console.log("Form submitted")
}
</script>
How can I set the form value with the result of the Ajax request?
Instead of trying to assign props.accountID, update the accountID computed prop, which updates the corresponding v-model:accountID via the computed setter. That v-model update is then reflected back to the component through the binding:
onMounted(async () => {
let response = await axios.get(…)
// props.accountID = response.data.ID ❌ cannot update readonly prop
accountID.value = response.data.ID ✅
})
Also note that your computed setter needs to emit the new value:
const accountID = computed({
get() {
return props.accountID
},
set(value) {
// return emit('update:accountID') ❌ missing value
return emit('update:accountID', value) ✅
},
})
demo

Render new Vue prop value [duplicate]

This question already has an answer here:
component data vs its props in vuejs
(1 answer)
Closed 1 year ago.
I have {{ story.score }} in a template and a function which updates the score:
setup(props) {
const changeScore = (value) => {
props.story.score = value;
}
return {
changeScore,
}
New value is assigned to props.story.score, but template doesn't rerender, what is missing here?
You can't change props directly in vue, it wont trigger an update. The proper way is to emit changes to parent element, like this:
setup({ props, emit }) {
const changeScore = (value) => emit('update-story-score', value);
return { changeScore }
}
Then just handle an event in parent, like this:
<template>
<Test :story="story" #update-story-score="updateStoryScore"></Test>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const story = reactive({ score: 1 });
const updateStoryScore = (value) => story.score = value;
return { story, updateStoryScore }
},
};
</script>
Vue props has a One-Way Data Flow
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around
You need to emit an event. Check out v-model on components

Attribute :value binding in select tag doesn't update inside Vue 3 component template (Composition API)

I have a drop down menu where options are enumerated and shuffled, so that the selected option becomes the first. This script is working as intended:
<div id="main">
<sub-select :subs="data" #comp-update="onShufflePaths($event)"></sub-select>
</div>
. .
const ui = {
setup() {
let data = ref('first_second_thrid');
const onShufflePaths = (ind) => {
let subs = data.value.match(/[^_]+/g);
const main = subs.splice(ind, 1)[0];
data.value = [main, ...subs].join('_');
}
return {
data, onShufflePaths,
};
},
};
const vueApp = createApp(ui);
vueApp.component('sub-select', {
props: ['subs'],
emits: ['comp-update'],
setup(props, { emit }) {
let subs = computed(() => props.subs.match(/[^_]+/g));
let subpath = computed(() => '0: ' + subs.value[0]);
function onChange(evt) {
emit('comp-update', evt.slice(0,1));
}
return { subs, subpath, onChange };
},
template: `
<select :value="subpath" #change="onChange($event.target.value)">
<option v-for="(v,k) in subs">{{k}}: {{v}}</option>
</select> {{subpath}}`
});
vueApp.mount('#main');
The problem is, if I delete {{subpath}} from the template, the drop down menu comes up with no options selected by default. It looks like :value="subpath" by itself is not enough to update subpath variable when props update, if it's not explicitly mentioned in the template.
How can I make it work?
Basically, I need the first option always to be selected by default.
Thank you!
https://jsfiddle.net/tfoller/uy7k1hvr/26/
So, it looks like it might be a bug in the library.
Solution 1:
wrap select tag in the template in another tag, like this (so it's not the lonely root element in the template):
template: `
<div><select :value="subpath" #change="onChange($event.target.value)">
<option v-for="(v,k) in subs">{{k}}: {{v}}</option>
</select></div>`
Solution 2:
Write a getter/setter to subpath variable, so component definition is as follows:
vueApp.component('sub-select', {
props: ['subs'],
emits: ['comp-update'],
setup(props, { emit }) {
let subs = computed(() => props.subs.match(/[^_]+/g));
let subpath = computed({
get: () => '0: ' + subs.value[0],
set (value) {
emit('comp-update', value.slice(0,1))
}
});
return { subs, subpath };
},
template: `
<select v-model="subpath">
<option v-for="(v,k) in subs">{{k}}: {{v}}</option>
</select>`
});
For having the first option selected by default you need to point to the index 0.
Here your index is the k from v-for
<option v-for="(v,k) in subs" :selected="k === 0">{{k}}: {{v}}</option>
There is no v-model in select, I think that is the main issue. Other is its not clear what you what to do.
please refer the following code and check if it satisfy your need.
// app.vue
<template>
<sub-select v-model="value" :options="options" />
{{ value }}
</template>
<script>
import { ref } from "vue";
import subSelect from "./components/subSelect.vue";
export default {
name: "App",
components: {
subSelect,
},
setup() {
const value = ref(null);
const options = ref(["one", "two", "three"]);
return { value, options };
},
};
</script>
see that I have used v-model to bind the value to sub-select component.
the sub-select component as follows
// subSelect.vue
<template>
<select v-model="compValue">
<template v-for="(option, index) in compOptions" :key="index">
<option :value="option">{{ index }}: {{ option }}</option>
</template>
</select>
</template>
<script>
import { computed } from "vue";
export default {
name: "subSelect",
props: ["modelValue", "options"],
setup(props, { emit }) {
// if value is null then update it to be first option.
if (props.modelValue === null) {
emit("update:modelValue", props.options[0]);
}
const compValue = computed({
get: () => props.modelValue,
set: (v) => emit("update:modelValue", v),
});
// return selected option first in list/Array.
const compOptions = computed(() => {
const selected = props.options.filter((o) => o === compValue.value);
const notSelected = props.options.filter((o) => o !== compValue.value);
return [...selected, ...notSelected];
});
return { compValue, compOptions };
},
};
</script>
in sub-select component i am checking first if modelValue is null and if so set value to be first option.
and also providing compOptions in such sequence that selected options will always be first in list of selection options.
so it satisfies
The first option always to be selected by default.
Selected option will always be first in list of options.
check the code working at codesandbox
edit
jsfiddle as per request
also i suspect that you need options as underscore separated string for that please refer String.prototype.split() for converting it to array and Array.prototype.join() for joining array back to string.
if this is the case please comment so I can update my answer. It should be possible by setting watcher on compOptions and emitting separate event to parent, but I don't think its a good idea!