Default value for props like an object didn't work - vue.js

I have a very strange situation :
My HomeComponent.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld :msg="msg" #update="inputUpdated" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "#/components/HelloWorld.vue"; // # is an alias to /src
import { Message } from "#/interfaces/message";
export default defineComponent({
name: "Home",
components: {
HelloWorld,
},
setup(props, { emit }) {
const msg: Message = {
year: 2020,
};
function inputUpdated(value): void {
console.log("Get Event : " + value);
}
return {
msg,
inputUpdated,
};
},
});
</script>
My HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg.title }} in the year : {{ msg.year }}</h1>
<button #click="increment">count is : {{ count }}</button>
<div>{{ pow(2, 3) }}</div>
<p>Edit <code>component</code> to test state is {{ state.year }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, PropType } from "vue";
import { State } from "#/interfaces/state";
import { Message } from "#/interfaces/message";
export default defineComponent({
name: "HelloWorld",
props: {
msg: {
type: Object as PropType<Message>,
required: true,
default: function () {
return {
title: "Arrow Function Expression",
year: 2020,
};
},
},
},
setup(props, { emit }) {
console.log(props);
const count = ref(0);
const state: State = reactive({
title: "",
year: 2020,
});
function pow(x: number, y: number): number {
return Math.pow(x, y);
}
function increment(): void {
console.log("Emit : Hello World");
emit("update", "Hello World");
count.value++;
}
return {
count,
increment,
pow,
state,
};
},
});
</script>
The interface Message :
export interface Message {
title?: string;
year?: number;
}
The problem what I have is even if I created a default for msg props didn't work, is not taken into account. I have the message in the year :. What I'm doing wrong with default values for props values ? Thx in advance and sorry for my english.

You're expecting the default prop's properties to be merged with the passed prop. That won't work and would mean the prop was modified, and props should not be modified. The default prop is used only when no prop has been passed (so you don't need the required attribute.)
You'll have to decide between splitting up the object into individual props:
props: {
title: ...,
year: ...
}
Or use a computed to merge:
setup(props) {
const msgDefault = {
title: "Arrow Function Expression",
year: 2020,
};
const merged = computed(() => {
return Object.assign({}, msgDefault, props.msg); // shallow merge
})
return { merged }
}

Related

How to use pinia store with v-for directive whilst keeping it reactive?

I have a pinia data store similar to the following code snippet that stores user information and a list of individual orders he is placing:
order.js
import { defineStore } from 'pinia'
import { reactive } from 'vue'
export const useOrderStore = defineStore('order', {
state: () => ({
username: '',
orders: reactive([
{
id: '',
item: '',
price: ''
}
])
}),
})
Also I am using the v-for directive to render the components that should display the individual orders
OrdersComp.vue
<template>
<div class="orders_container">
<div v-for="(order, index) in orders" :key="order.id">
<OrderComp />
</div>
</div>
</template>
<script>
import { storeToRefs } from 'pinia'
import { useOrderStore } from "#/store/order";
setup() {
const { orders } = storeToRefs(useOrderStore())
return { orders };
},
</script>
How can I access the store data for the individual orders in the child component OrderComp
Basically I want something like this:
OrderComp.vue
<div>
<p>{{ orders.id }}</p>
<input v-model="orders.item" />
<input v-model="orders.price" />
</div>
<script>
import { storeToRefs } from 'pinia'
import { useOrderStore } from "#/store/order";
setup() {
const { orders } = storeToRefs(useOrderStore())
return { orders };
},
</script>
and still keep its reactive state? How does the child component know which order of the orders array to modify? Can/Should I combine the pinia data store with props that pass the data from parent to child? (Though this seems somewhat wrong for me, as pinia is probably able to replace all data passing between components) And furthermore as item and price are bound to input fields, they should of course dynamically change based on a user input.
Based on Estus Flasks comments I got it working by emitting events from the child OrderComp to the parent OrdersComp and on each change it invoked a function that modified my orders array at the correct index in the datastore.
So following the example above I did something like this:
order.js
import { defineStore } from 'pinia'
import { reactive } from 'vue'
export const useOrderStore = defineStore('order', {
state: () => ({
username: '',
orders: reactive([
{
id: '',
item: '',
price: ''
}
])
}),
actions: {
modifyOrder (id, order) {
var foundIndex = this.orders.findIndex(elem => elem.id == order.id)
this.orders[foundIndex] = order
}
}
})
OrdersComp.vue
<template>
<div class="orders_container">
<div v-for="(order, index) in order_store.orders" :key="order.id">
<OrderComp #change="order_store.modifyOrder(index, order)"
v-model:itemProp="order.item"
v-model:priceProp="order.price"
/>
</div>
</div>
</template>
<script>
import { useOrderStore } from "#/store/order";
export default {
setup() {
const order_store = useOrderStore()
return { order_store };
},
}
</script>
Note: I use a wrapper function here to emit the inputs, however you can of course emit it directly e.g. via #input/#change
OrderComp.vue
<div>
<input v-model="item" />
<input v-model="price" />
</div>
<script>
import { useModelWrapper } from "#/modelWrapper";
export default {
name: "OrderComp",ยด
props: {
itemProp: { type: String, default: "" },
priceProp: { type: String, default: "" },
},
emits: [
"update:itemProp",
"update:priceProp",
],
setup(props, { emit }) {
return {
item: useModelWrapper(props, emit, "itemProp"),
price: useModelWrapper(props, emit, "priceProp"),
};
},
}
modelWrapper.js
import { computed } from "vue";
export function useModelWrapper(props, emit, name = "modelValue") {
return computed({
get: () => props[name],
set: (value) => emit(`update:${name}`, value),
});
}

Vue received a Component which was made a reactive object

The problem I need to solve: I am writing a little vue-app based on VueJS3.
I got a lot of different sidebars and I need to prevent the case that more than one sidebar is open at the very same time.
To archive this I am following this article.
Now I got a problem:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. (6)
This is my code:
SlideOvers.vue
<template>
<component :is="component" :component="component" v-if="open"/>
</template>
<script>
export default {
name: 'SlideOvers',
computed: {
component() {
return this.$store.state.slideovers.sidebarComponent
},
open () {
return this.$store.state.slideovers.sidebarOpen
},
},
}
</script>
UserSlideOver.vue
<template>
<div>test</div>
</template>
<script>
export default {
name: 'UserSlideOver',
components: {},
computed: {
open () {
return this.$store.state.slideovers.sidebarOpen
},
component () {
return this.$store.state.slideovers.sidebarComponent
}
},
}
</script>
slideovers.js (vuex-store)
import * as types from '../mutation-types'
const state = {
sidebarOpen: false,
sidebarComponent: null
}
const getters = {
sidebarOpen: state => state.sidebarOpen,
sidebarComponent: state => state.sidebarComponent
}
const actions = {
toggleSidebar ({commit, state}, component) {
commit (types.TOGGLE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
},
closeSidebar ({commit, state}, component) {
commit (types.CLOSE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
}
}
const mutations = {
[types.TOGGLE_SIDEBAR] (state) {
state.sidebarOpen = !state.sidebarOpen
},
[types.CLOSE_SIDEBAR] (state) {
state.sidebarOpen = false
},
[types.SET_SIDEBAR_COMPONENT] (state, component) {
state.sidebarComponent = component
}
}
export default {
state,
getters,
actions,
mutations
}
App.vue
<template>
<SlideOvers/>
<router-view ref="routerView"/>
</template>
<script>
import SlideOvers from "./SlideOvers";
export default {
name: 'app',
components: {SlideOvers},
};
</script>
And this is how I try to toggle one slideover:
<template>
<router-link
v-slot="{ href, navigate }"
to="/">
<a :href="href"
#click="$store.dispatch ('toggleSidebar', userslideover)">
Test
</a>
</router-link>
</template>
<script>
import {defineAsyncComponent} from "vue";
export default {
components: {
},
data() {
return {
userslideover: defineAsyncComponent(() =>
import('../../UserSlideOver')
),
};
},
};
</script>
Following the recommendation of the warning, use markRaw on the value of usersslideover to resolve the warning:
export default {
data() {
return {
userslideover: markRaw(defineAsyncComponent(() => import('../../UserSlideOver.vue') )),
}
}
}
demo
You can use Object.freeze to get rid of the warning.
If you only use shallowRef f.e., the component will only be mounted once and is not usable in a dynamic component.
<script setup>
import InputField from "src/core/components/InputField.vue";
const inputField = Object.freeze(InputField);
const reactiveComponent = ref(undefined);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = undefined;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
</script>
<template>
<component :is="reactiveComponent" />
</template>

Vue-i18n not translating inside component script tags

Building a language switcher, all works fine but when I use the $t() inside the data object it will not be dynamic when I switch between a language.
Component.vue
<template>
// loop menu here
<div v-for="item in menu">
{{ item.label }}
</div>
</template>
<script>
const mainMenu = [
{
label: $t('dashboard'),
},
{
label: $t('users'),
},
{
label: $t('settings'),
},
}
export default {
data () {
return {
menu = MainMenu
}
}
}
</script>
i18n.js
// https://vue-i18n.intlify.dev/
import { createI18n } from 'vue-i18n'
export function loadLocalMessages () {
const locales = require.context('../locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
const messages = {}
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key)
}
})
return messages;
}
const i18n = createI18n({
locale: 'en',// .env not working
fallbackLocale: 'en',// .env not working
messages: loadLocalMessages(),
});
export default i18n
<template>
<div v-for="item in menu">
{{ item.label }}
</div>
</template>
<script>
export default {
computed: {
menu() {
return [{
label: this.$t('dashboard'),
}, {
label: this.$t('users'),
}, {
label: this.$t('settings'),
}]
}
}
}
</script>
data is only ever called once when creating the component, and it's not intended to be reactive.
To make a property reactive on $t(), it should be computed:
export default {
computed: {
hello() {
return this.$t('hello')
}
}
}
demo

Access the variables of a component

I'd like to access a component variable. This variable is modified by an html input.
Here is my current code
Parent :
<template>
<component>Child</component>
</template>
<script>
export default {
data () {
return {
dataChild : '',
}
</script>
Child :
<template>
<input type="text" name="data" v-model="data" class="form-control">
</template>
<script>
export default {
data () {
return {
data: ''
}
}
}
</script>
I've looked all over the internet but nothing works on my side or I'm doing wrong :(
Thanks in advance
you can use like below:
Parent
<template>
<component #input-child="childData"></component>
</template>
<script>
import Component from "#/components/Component";
export default {
components: { Component },
data() {
return {
dataChild: ""
};
},
methods: {
childData(data) {
console.log(data);
// you can assign it to dataChild variable
}
}
};
</script>
Child
<template>
<input
type="text"
name="data"
v-model="data"
#input="$emit('input-child', data)"
class="form-control"
/>
</template>
<script>
export default {
data() {
return {
data: ""
};
}
};
</script>
The standard way to send data from child component to parent in vue is to emit that inside child and listen for event inside parent. Like this:
Child:
export default {
data: () => ({ dataChild: 'TEST' }),
methods: {
send () {
this.$emit('data', this.dataChild)
}
},
mounted () { this.send() }
}
Parent:
<div>
<child #data="get"></child>
</div>
export default {
data: () => ({ data: '' }),
methods: {
get(value) {
this.data = value
}
}
}

How do I trigger an AJAX request when props is changed?

I have a this App component
<template>
<div id="app">
<Component1 #addItem="addItem" />
<Component2 :items="items" />
</div>
</template>
<script>
import Component1 from './components/Component1'
import Component2 from './components/Component2'
export default {
name: 'app',
components: { Component1, Component2 },
data: function () {
return {
items: [],
}
},
methods: function () {
addItem(item) {
items.push(item)
},
},
}
</script>
This is my Component2 component:
<template>
<div>
{{ ajax_data }}
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Component2',
props: ['items'],
data: function () {
return {
ajax_data: null
}
},
mounted () {
this.callAJAX()
},
methods: {
callAJAX() {
axios
.get('/api/get-some-data', {
params: {
items: items
}
})
.then((response) => {
this.ajax_data = response.data
})
},
},
}
</script>
I want to trigger the AJAX everytime I add an item. The problem with my code is since Component2 is already mounted and when an item is added the AJAX is not running. So then I added this hook:
updated () {
this.callAJAX()
},
The problem with this is its running an infinite loop.
Is there a proper way to do this?
You can simply detect if value change with a watcher
https://v2.vuejs.org/v2/guide/computed.html#Watchers
https://v2.vuejs.org/v2/api/#watch
in your case, you may set the deep property to true...