Props and Computations in Vue - vue.js

I am having trouble with something seemingly simple: how do I use a prop, passed to my component, as the basis for some computation? As in --
export default {
props: {
officeConsumption: {
type: Number,
},
commuteOutput: {
type: Number,
},
savings: {
type: Number,
}
},
data() {
return {
drives: this.savings,
flights: this.savings
}
},
And so on, except I want do something with savings (like Math.round) and use it in my template like {{ drives }}. I get as far as {{ savings }} i.e. using the original prop but am having trouble achieving the desired end result {{ Math.round(savings * / + some computation) }}. This is due 3 and Vite.

You have two ways of using computed in Vue3. Under the hood, they're the same thing:
1. Composition API computed:
import { defineComponent, computed } from 'vue';
export default defineComponent({
props: {
officeConsumption: {
type: Number,
},
},
setup(props) {
const myComputed = computed(() => Math.round(props.officeConsumption));
return {
myComputed
}
}
})
Another flavor of the above is inside a reactive() object:
import { defineComponent, computed, reactive, toRefs } from 'vue';
export default defineComponent({
props: {
officeConsumption: {
type: Number,
},
},
setup(props) {
const state = reactive({
myComputed: computed(
() => Math.round(props.officeConsumption)
)
})
return {
...toRefs(state)
}
}
})
2. Options API computed (just like in Vue 2, it's still available):
import { defineComponent } from 'vue'
export default defineComponent({
props: {
officeConsumption: {
type: Number,
},
},
computed: {
myComputed() {
return Math.round(this.officeConsumption);
}
}
})
All of the above produce the same result. You can use myComputed in the <template>.

Related

Why action of Vuex returns a promise<pending>?

I have an action in Vuex actions which commit a mutation that it take a payload from the component, that is a number of the index for returning an object, it works fine on Vuex js file meaning that shows the selected item on the console, as I said it gets index from the payload,
but on the component, it gives me Promise <Pending>, why that's happening? for now, I do not use any API for my Nuxt/Vue app, but I will, and for now, I just want to know why this is happening and what is the best solution for solving this
Here my Vuex codes:
export const state = () => ({
articles: [
{
uid: 0,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/1.jpg',
link: '/articles/1',
},
{
uid: 1,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/2.jpg',
link: '/articles/2',
},
],
})
export const getters = {
getArticles(state) {
return state.articles
},
}
export const mutations = {
getSpeceficArticle(state, payload) {
return state.articles[payload]
},
}
export const actions = {
getSpeceficArticle({ commit }, payload) {
commit('getSpeceficArticle', payload)
},
}
and here my component codes:
<template>
<div class="article">
{{ getSpeceficArticle() }}
<div class="article__banner">
<img src="" alt="" />
</div>
<div class="article__text">
<p></p>
</div>
</div>
</template>
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
// computed: {},
methods: {
async getSpeceficArticle() {
return await this.$store.dispatch('articles/getSpeceficArticle', 0)
},
},
}
</script>
actions are used to update the state they are like mutations but the main difference between them is that actions can include some asynchronous tasks, if you want to get a specific article at given index you should use a getter named getArticleByIndex :
export const getters = {
getArticles(state) {
return state.articles
},
getArticleByIndex:: (state) => (index) => {
return state.articles[index]
}
}
then define a computed property called articleByIndex :
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.$store.getters.articles.getArticleByIndex(0)
}
},
methods: {
},
}
</script>
#Mohammad if you find yourself using a lot of getters/actions etc from Vuex and they're starting to get a little wordy, you can bring in mapGetters from Vuex and rename your calls to something a little more convenient. So your script would become,
<script>
import { mapGetters } from 'vuex'
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.getArticleByIndex(0)
}
},
methods: {
...mapGetters({
getArticleByIndex: 'articles/getArticleByIndex',
})
},
}
</script>
You can add ...mapGetters, ...mapActions to your computed section also.
since there is no web service call in vuex action, try to remove async and await keywords from the component.
Later when you add a webservice call than you can wrap action body in new Promise with resolve and reject and then you can use async and await in component. let me know if this works for you.

How to use composition API to create a new component in vue3?

When we use vue2 to create API, we just follow options API like below:
data are in data
methods are in methods
<script>
export default {
name: 'demo',
components: {},
filter:{},
mixins:{},
props: {},
data(){
return{
}
},
computed:{},
watch:{},
methods: {},
}
</script>
But the vue3 changed, how should I build a component with vue3 composition API?
Some example say that I should import reactive etc. From vue first and put all codes in setup(){}?
Some example show that I can add setup to <script>?
Please give me an example.
ok bro , Composition Api works like that:
<script>
import { fetchTodoRepo } from '#/api/repos'
import {ref,onMounted} from 'vue'
export default {
setup(props){
const arr = ref([]) // Reactive Reference `arr`
const getTodoRepo = async () => {
arr.value = await fetchTodoRepo(props.todo)
}
onMounted(getUserRepo) // on `mounted` call `getUserRepo`
return{
arr,
getTodoRepo
}
}
}
</script>
There are two ways to create a component in vue3.
One:<script> + setup(){},such as this:
<script>
import { reactive, onMounted, computed } from 'vue'
export default {
props: {
title: String
},
setup (props, { emit }) {
const state = reactive({
username: '',
password: '',
lowerCaseUsername: computed(() => state.username.toLowerCase())
})
onMounted(() => {
console.log('title: ' + props.title)
})
const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}
return {
login,
state
}
}
}
</script>
Two:use <script setup="props">
loading....

How to manually define props type in vue3 with typescript

I can do
defineComponent({
props: {
name: { type: String as PropType<string> }
}
})
to tell vue3 that my props type is { name: string }, but if I have several component have the same props type, how can I share the defination?
If I define props in :
const props = {
name: String as PropType<string>
}
then use it like this:
defineComponent({
props: props,
})
It won't work, the type I got in setup function of props is not right.
this answer is purely addition to #Xinchao's answer.
one way is to destructure common props like following:
// taken from #Xinchao's answer
const commonProps = {
name: { type: String as PropType<string> }
}
defineComponent({
props:{
...commonProps,
extra: { }
}
})
another way is to write function which returns specific object like following
function getStringProp(required=false) {
return {
type: String as PropType<string>,
required,
default: '',
};
}
defineComponent({
props:{
name: getStringProp(true),
nickName: getStringProp(),
extra: { }
}
})
this case specifically come handy where prop is Array or Object; where we can cast the type like following
function getArrayProp<T>(required=false) {
return {
type: Array as PropType<T[]>,
required,
default: () => [],
};
}
defineComponent({
props:{
options: getArrayProp<Options>(true),
stringOptions: getArrayProp<string>(true),
}
})
The props options provided for defineComponent is a plain js object solely for type annotation purpose, so you can employ whatever technics in javascript for sharing structures between objects:
// in common.ts
export const commonProps = {
name: { type: String as PropType<string> }
}
// in your component.vue
import commonProps from "./common.ts";
defineComponent({
props:{
...commonProps,
extra: { }
}
})
If you're using the composition API with Vue 2 in preparation for switching to Vue 3, you have to use the PropType from that package instead of the vue package.
// Wrong for Vue 2
import Vue, { PropType } from 'vue'
import { defineComponent } from '#vue/composition-api'
// Right for Vue 2
import { defineComponent, PropType } from '#vue/composition-api'

How to update chart when state changes in vue?

I've mapped chartData to a state property using vuex. What I'd like to do is update the chart when a dataset is updated. I have heard that it can be done with mixins or watchers but I don't know how to implement it. I understand that mixins creates a watcher but I don't know how it is used within vuex.
Chartline.vue:
<script>
import { Line } from 'vue-chartjs'
import { mapState } from 'vuex'
export default {
name: 'ChartLine',
extends: Line,
computed:{
...mapState(['charData','options'])
},
methods:{
regraph: function(){
this.renderChart(this.charData,this.options);
}
},
mounted () {
this.regraph();
},
watch: {
}
}
</script>
Pension.vue:
<template>
<div id='pension' class="navbarPar">
<ChartLine/>
</div>
</template>
<script>
import ChartLine from '../components/ChartLine.vue';
import { mapState } from 'vuex'
//import { Line } from 'vue-chartjs'
export default {
name: 'Pension',
components: {
ChartLine,
},
data(){
return{
form: {
...
},
var:{
...
},
}
},
methods: {
calculate: function(indice){
...
//modify data of mapState
//after here, I want to rerender chart
}
},
computed:{
...mapState(['charData','options']),
},
}
</script>
Using a watcher like this should be enough:
<script>
import { Line } from "vue-chartjs";
import { mapState } from "vuex";
export default {
name: "ChartLine",
extends: Line,
computed: {
...mapState(["chartData", "options"])
},
methods: {
regraph() {
this.renderChart(this.chartData, this.options);
}
},
mounted() {
this.regraph();
},
watch: {
chartData: {
handler: this.regraph,
deep: true
}
}
};
</script>
Also having the explicit vuex state map inside the ChartLine component seems a bit wasteful - passing the vuex data through props would render the component more generic:
<template>
<div id='pension' class="navbarPar">
<ChartLine :options="options" :chart-data="chartData"/>
</div>
</template>
<script>...
Chartline.vue:
<script>
import { Line } from "vue-chartjs";
export default {
name: "ChartLine",
extends: Line,
props: {
options: {
type: Object,
default: () => ({})
},
chartData: {
type: Object /*is it?*/,
default: () => ({})
}
},
methods: {
regraph() {
this.renderChart(this.chartData, this.options);
}
},
mounted() {
this.regraph();
},
watch: {
chartData: {
handler: this.regraph,
deep: true
}
}
};
</script>
If you are using vue-chartjs, the library has its own way to handle reactive data in charts:
// ChartLine.js
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'], // passed from the parent
mounted () {
// this.chartData is created in the mixin (pass it as any prop with :chart-data="data").
this.renderChart(this.chartData, this.options)
}
}
Now the Pension.vue file
// Pension.vue
<template>
<div id='pension' class="navbarPar">
<ChartLine :chart-data="charData" :options="options" />
</div>
</template>
<script>
import ChartLine from '../components/ChartLine';
import { mapState } from 'vuex'
export default {
name: 'Pension',
components: {
ChartLine,
},
data(){
return{
form: {
...
},
var:{
...
},
}
},
methods: {
calculate: function(indice){
...
//modify data of mapState
//after here, I want to rerender chart
}
},
computed:{
...mapState(['charData','options']),
},
}
</script>
You can read more about it here: https://vue-chartjs.org/guide/#updating-charts,
there are some caveats

How can I pass data from a component to another component on vue?

I have 2 components
My first component like this :
<template>
...
<b-form-input type="text" class="rounded-0" v-model="keyword"></b-form-input>
<b-btn variant="warning" #click="search"><i class="fa fa-search text-white mr-1"></i>Search</b-btn>
...
</template>
<script>
export default {
data () {
return {
keyword: ''
}
},
methods: {
search() {
this.$root.$emit('keywordEvent', this.keyword)
location.href = '/#/products/products'
}
}
}
</script>
My second component like this :
<template>
...
</template>
<script>
export default {
data () {
return{
keyword: ''
}
},
mounted: function () {
this.$root.$on('keywordEvent', (keyword) => {
this.keyword = keyword
})
this.getItems()
},
methods: {
getItems() {
console.log(this.keyword)
....
}
}
}
</script>
I using emit to pass value between components
I want to pass value of keyword to second component
/#/products/products is second component
I try console.log(this.keyword) in the second component. But there is no result
How can I solve this problem?
Update :
I have index.js which contains vue router like this :
import Vue from 'vue'
import Router from 'vue-router'
...
const Products = () => import('#/views/products/Products')
Vue.use(Router)
export default new Router({
mode: 'hash', // https://router.vuejs.org/api/#mode
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/pages/login',
name: 'Home',
component: DefaultContainer,
children: [
{
path: 'products',
redirect: '/products/sparepart',
name: 'Products',
component: {
render (c) { return c('router-view') }
},
children : [
...
{
path: 'products',
name: 'products',
component: Products,
props:true
}
]
},
]
},
{
path: '/products/products',
name: 'ProductsProducts', // just guessing
component: {
render (c) { return c('router-view') }
},
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
]
})
From this code...
location.href = '/#/products/products'
I'm assuming /#/products/products maps to your "second" component via vue-router, I would define the keyword as a query parameter for the route. For example
{
path: 'products',
name: 'products',
component: Products,
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
Then, in your component, define keyword as a string prop (and remove it from data)
props: {
keyword: String
}
and instead of directly setting location.href, use
this.$router.push({name: 'products', query: { keyword: this.keyword }})
There are some ways to do it in Vue.
Use EventBus with $emit like you did;
event-bus.js
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
component1.vue :
import EventBus from './event-bus';
...
methods: {
my() {
this.someData++;
EventBus.$emit('invoked-event', this.someData);
}
}
component2.vue
import EventBus from './event-bus';
...
data(){return {changedValue:""}},
...
mounted(){
EventBus.$on('invoked-event', payLoad => {
this.changedValue = payload
});
}
Use Vuex store, will be accessible at any component, at any page; (my favorite way)
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = () =>
new Vuex.Store({
store: {myStore:{value:0}},
actions:{["actionName"]:function({commit}, data){commit("actionName", data);}}, // I usualy using special constant for actions/mutations names, so I can use that here in code, and also in components
mutations:{["actionName"]:function(state, data){state.myStore.value = data;}},
getters:{myStoreValue: state => !!state.myStore.value}
})
component1.vue
...
methods:{
change:function(){
this.$store.dispatch("actionName", this.someData); //nuxt syntax, for simple vue you have to import store from "./../store" also
}
}
component2.vue
...
data(){return {changedValue:""}},
...
mounted(){
this.changedValue = this.$store.getters.myStoreValue;
}
Use props like #Phil said.