Conditionally accessing Pinia value from template in Vue 3 - javascript

I'm trying to render a component only if the value accessed from my Pinia store is true -
// MessageArea.vue
import { useValidationStore } from '../../stores/ValidationStore.js'
data() {
return {
errors: {
english: useValidationStore().english.error,
},
}
},
<template>
<p v-if="errors.english">
<EnglishErrorMessage />
</p>
</template>
By default, it is false -
import { defineStore } from "pinia";
export const useValidationStore = defineStore("validation", {
state: () => {
return {
english: {
input: '',
error: false,
},
}
}
})
I'm also accessing that same value from another file to check for the error -
<script>
import { useValidationStore } from '../../../stores/ValidationStore';
export default {
data() {
return {
input: useValidationStore().english.message,
error: useValidationStore().english.error,
}
},
methods: {
validate(input) {
this.input = input.target.value
const legalChars = /^[A-Za-z\s]*$/.test(this.input);
if (!legalChars && this.input !== "") {
this.error = true;
} else if (this.input === "" || (legalChars && !legalChars)) {
this.error = false;
}
console.log(this.error)
console.log(this.input)
}
}
}
</script>
<template>
<input
#input="validate"
:value="input"
/>
</template>
The console log values are updating reactively. Also, this.error will be logged as true when an illegal character is entered into the input. The reactivity is behaving as expected. So, I'm not sure why '' will not render in this case?
Am I not accessing the pinia value correctly in the template?
I've tried to understand what 'mapState()' and 'mapStores()' from the docs and if they can help me but I'm still confused.

You need to mutate the store state in validate method.
If you want to use pinia in options api you can use mapState and mapWritableState in computed property to remain rective.
Take a look here codesandbox please.

Related

Enable loading state of an input field on a computed property

EDITED: code pen was added at the end of the post
How to eanable (change to true) the loading of an input field in a computed property?
In the example for demonstration that follows I get the 'error' unexpected side effect in "inputFilterParentsAndChilds" computed property.
The search field and the list
<template>
<q-input
label="Search"
v-model="searchValue"
placeholder="Minimum 3 characters"
:loading="loadingState"
/>
<q-list
v-for="pater in inputFilterParentsAndChilds"
:key="pater.id_parent"
>
<q-item>
<q-item-section>
<q-item-label>
{{ pater.name }}
</q-item-label>
</q-item-section>
</q-item>
<q-list
v-for="filius in pater.allfilius"
:key="filius.id_filius"
>
<q-item>
<q-item-section>
<q-item-label>
{{ filius.title }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-list>
</template>
Computed propriety
<script>
export default {
data() {
return {
loadingState: false,
searchValue: ''
};
}
}
computed: {
//filter the parent list with nested data (childs)
inputFilterParentsAndChilds() {
if (this.searchValue.length > 2) {
this.loadingState = true; // error!! unexpected side effect in "inputFilterParentsAndChilds" computed property
const filteredPaterArr = this.records.map((rows) => {
const filteredParent = rows.filius.filter(
({ name }) =>
name.toLowerCase().match(this.searchValue.toLowerCase())
);
const filteredChilds = rows.filius.filter(
({ title }) =>
title.toLowerCase().match(this.searchValue.toLowerCase())
);
if (filteredChilds.length) {
this.loadingState = false;
return { ...rows, filius: filteredParent };
} else {
this.loadingState = false;
return { ...rows, filius: filteredChilds };
}
});
return filteredPaterArr.filter((obj) => obj.filius.length);
} else {
return this.records;
}
}
}
</script>
About nested v-for list Filter nested v-for list
EDITED
CODEPEN https://codepen.io/ijose/pen/BaYMVrO
Why is loading important in my case?
If the list has hundreds of records the filter is slow and appears (wrongly) to have frozen, requiring a loading to inform the user that the filtering operation is still in progress
You don't, computed properties simply can't change state.
You can achieve similar effects by watching searchValue.
data () {
return {
searchValue: '',
isLoading: false,
filteredElements: []
}
},
watch: {
searchValue: {
immediate: true, // tells Vue to run handler function right after constructing the component
handler () {
// this function will be triggered when searchValue changes
this.isLoading = true
// give Vue chance to render the loader before doing heavy computation in 'filterElements' function
this.$nextTick(() => {
this.filteredElements = this.filterElements()
this.isLoading = false
})
}
}
Docs on nextTick
Docs on watchers
Edit:
Have you measured that it's really computing that makes your drop-down slow and not rendering? Take a look at the documentation on performance, especially "Virtualizing large lists"

Issue when trying to interact with an API in Vuejs?

datalist.js
import axios from "axios";
export const datalist = () => {
return axios.get("myapiurl/name...").then((response) => response);
};
HelloWorld.vue
<template>
<div>
<div v-for="item in items" :key="item.DttID">
<router-link
:to="{
name: 'UserWithID',
params: { id: item.DepaD },
query: { DepaD: item.DepaID },
}"
>
<div class="bt-color">{{ item.DepaName }}</div>
</router-link>
</div>
<br /><br /><br />
<User />
</div>
</template>
<script>
import User from "./User.vue";
import { datalist } from "./datalist";
export default {
name: "HelloWorld",
components: {
User,
},
data() {
return {
items: datalist,
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
<template>
<div>
<div v-for="(item, key) in user" :key="key">
{{ item.Accv }}
</div>
</div>
</template>
<script>
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
lists: datalist,
};
},
computed: {
user: function () {
return this.lists.filter((item) => {
if (item.DepaD === this.$route.params.id) {
return item;
}
});
},
},
};
</script>
Error with the code is,
[Vue warn]: Error in render: "TypeError: this.lists.filter is not a function"
TypeError: this.lists.filter is not a function
The above error i am getting in User.vue component in the line number '20'
From the api which is in, datalist.js file, i think i am not fetching data correctly. or in the list filter there is problem in User.vue?
Try to change the following
HelloWorld.vue
data() {
return {
items: [],
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
User.vue
data() {
return {
lists: []
};
},
mounted() {
datalist().then((r) => {
this.lists = r.data;
});
},
At least this suppress the error, but i cant tell more based on your snippet since there are network issues :)
Since your datalist function returns a Promise, you need to wait for it to complete. To do this, simply modify your component code as follows:
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
// empty array on initialization
lists: [],
};
},
computed: {
user: function() {
return this.lists.filter((item) => {
if (item.DeploymentID === this.$route.params.id) {
return item;
}
});
},
},
// asynchronous function - because internally we are waiting for datalist() to complete
async-mounted() {
this.users = await datalist() // or datalist().then(res => this.users = res) - then async is not needed
}
};
now there will be no errors when initializing the component, since initially lists is an empty array but after executing the request it will turn into what you need.
You may define any functions and import them, but they wont affect until you call them, in this case we have datalist function imported in both HelloWorld and User component, but it did not been called in User component. so your code:
data() {
return {
lists: datalist,
};
},
cause lists to be equal to datalist that is a function, no an array! where .filter() should be used after an array, not a function! that is the reason of error.
thus you should call function datalist and put it's response in lists instead of putting datalist itself in lists
Extra:
it is better to call axios inside the component, in mounted, created or ...
it is not good idea to call an axios command twice, can call it in HelloWorl component and pass it to User component via props

Vue not reacting to a computed props change

I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it
Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}

how to pass data to mixins and then displaying them in your component?

I want to pass data to my mixin's method, and then display it in my component. Something like:
//component A
mixins: [mixinOne],
data(){
return{
val = null
}
},
mounted(){
this.mixinMethod('good value', this.val);
}
//mixinOne
mixinMethod(valOne, valTwo) {
valTwo = valOne;
}
And in my template I want to display val:
// component A
<template>
{{val}}
</template>
I have written the above code and it doesn't work. It returns null for {{val}}! So basically I want to see 'good value' in my component for {{val}} which is setup through my mixin. How can I do that?
You Should put your Data section in mixin then change it and render it in your component.
// MmixinOne
data () {
return {
val = null
}
},
methods: {
mixinMethod (valOne, valTwo) {
valTwo = valOne
}
}
// Component A
<template>
{{val}}
</template>
<script>
import MmixinOne from './MmixinOne'
export default {
mixins: [MmixinOne],
mounted () {
this.mixinMethod('good value', this.val)
}
}
</script>
Anyway you dont need a method to set value on "val".
you can just set your value directly in mounted:
mounted () {
this.val = 'good value'
}

VueJs validate child component names in parent component

I have a parent component as shown below that implements dynamic component switching. The challenge is that I want to ensure that the componentArgs name data at index 0 is valid. The line this.components.indexOf(componentArgs[0]) > -1 throws a TypeError.
Parent.vue?e9d7:xx Uncaught TypeError: Cannot read property 'indexOf'
of undefined
Clearly, this.components is not the correct way to access the parents components. How do I accomplish this?
<template>
<component v-bind:is="currentForm" v-on:switchcomponent="switchComponent"></component>
</template>
<script>
import Login from '#/components/Login'
import Signup from '#/components/Signup'
export default {
name: 'Parent',
components: {
login: Login,
signup: Signup
},
data () {
return {
title: 'Sign Up or Login',
email: '',
currentForm: 'login'
}
},
methods: {
switchComponent: function (componentArgs) {
// componentArgs format: [name, title, email (Use empty string if N/A.)]
let name = 0
let title = 1
let email = 2
console.log('component: ' + componentArgs)
if (this.isValidComponent(componentArgs)) {
this.currentForm = componentArgs[name]
this.title = componentArgs[title]
this.email = componentArgs[email]
}
},
isValidComponent: function (componentArgs) {
let exactLength = 3
if (componentArgs.length === exactLength) {
console.log('components ' + this.components)
if (this.components.indexOf(componentArgs[0]) > -1) {
console.log('component exists')
return true
}
return false
}
return false
}
}
}
</script>
The components property can be accessed from
this.$options.components
So your function could be
isValidComponent(name){
return !!this.$options.components[name];
}
where name is the name of the component. I can't really tell what you are passing in componentArgs.
Example.

Categories

Resources