How to set up Pinia getter in Vue 3 Composition API - vue.js

I am building a Pokemon filtered search app using Vue 3, Composition API, and Pinia. I am attempting to set up the app so that the fetched response from the Pokemon API is passed to a store (set up using Pinia) inside the fetchPokemon() function.
const fetchPokemon = () => {
axios.get("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((response) => {
store.addPokemon(response.data.results)
})
}
After passing the response to the store, the updatePokemon() function uses filter and include methods to filter out and match Pokemon in the store with Pokemon in the user-input text field ("state.text"):
const updatePokemon = () => {
if(!state.text) {
return []
}
return store.getState.pokemons.filter((pokemon) =>
pokemon.name.includes(state.text)
)
}
When executing the app, I am getting the following error in the updatePokemon() function:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'includes')
I'm assuming this means the .includes() method for searching/filter cannot be used for this search. How should I go about handling the filter and include methods to match Pokemon in the store with the user-inputted Pokemon?
Here is the code:
Pinia Store
import { defineStore } from 'pinia'
export const usePokemonStore = defineStore({
id: 'store',
state: () => ({
pokemons: []
}),
getters: {
getState(state) {
return state
}
},
actions: {
addPokemon(name) {
this.pokemons.push(name)
}
}
})
Component
<template>
<div class="w-full flex justify-center">
<input type="text" placeholder="Enter Pokemon here"
class="mt-10 p-2 border-blue-500 border-2" v-model="text"/>
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div class="ml-4 text-2x text-blue-400"
v-for="(pokemon, idx) in filteredPokemon" :key="idx">
<router-link :to="`/about/${getPokemonId(pokemon.name)}`">
{{ pokemon.name }} - with id {{ getPokemonId(pokemon.name) }}
</router-link>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { reactive, toRefs, computed } from 'vue';
import { usePokemonStore } from '#/store';
export default {
name: 'Home',
setup() {
const store = usePokemonStore()
const state = reactive({
text: "",
filteredPokemon: computed(()=> updatePokemon())
})
const updatePokemon = () => {
if(!state.text) {
return []
}
return store.getState.pokemons.filter((pokemon) =>
pokemon.name.includes(state.text)
)
}
const fetchPokemon = () => {
axios.get("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((response) => {
store.addPokemon(response.data.results)
})
}
fetchPokemon()
const getPokemonId = (item) => {
console.log(item)
return store.pokemons.findIndex((p) => p.name === item) + 1
}
return { ...toRefs(state), fetchPokemon, getPokemonId, updatePokemon, store }
}
}
</script>
UPDATED
Store - with not action
import { defineStore } from 'pinia'
export const usePokemonStore = defineStore({
id: 'store',
state: () => ({
pokemons: []
})
})
Component - with no store.addPokemon(...)
<template>
<div class="w-full flex justify-center">
<input type="text" placeholder="Enter Pokemon here"
class="mt-10 p-2 border-blue-500 border-2" v-model="text"/>
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div class="ml-4 text-2x text-blue-400"
v-for="(pokemon, idx) in filteredPokemon" :key="idx">
<router-link :to="`/about/${getPokemonId(pokemon.name)}`">
{{ pokemon.name }} - with id {{ getPokemonId(pokemon.name) }}
</router-link>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { reactive, toRefs, computed } from 'vue';
import { usePokemonStore } from '#/store';
export default {
name: 'Home',
setup() {
const store = usePokemonStore()
const state = reactive({
// pokemons: [],
text: "",
filteredPokemon: computed(()=> updatePokemon())
})
const updatePokemon = () => {
if(!state.text) {
return []
}
return store.pokemons.filter((pokemon) =>
pokemon.name.includes(state.text)
)
}
const fetchPokemon = () => {
axios.get("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((response) => {
store.pokemons = response.data.results
})
}
fetchPokemon()
const getPokemonId = (item) => {
console.log(item)
return store.pokemons.findIndex((p) => p.name === item) + 1
}
return { ...toRefs(state), fetchPokemon, getPokemonId, store }
}
}
</script>

First of all, you don't need getState at all.
You can use usePokemonStore().pokemons directly. The object returned by calling usePokemonStore() function includes:
all state properties
all actions
all getters.
Here's how to get the filtered pokemon array, based on whether their name includes state.text:
setup() {
const store = usePokemonStore();
const state = reactive({
text: "",
filteredPokemons: computed(() => store.pokemons.filter(
pokemon => pokemon.name.includes(state.text)
))
});
return {
...toRefs(state)
}
}
Working example:
const { createApp, reactive, toRefs, computed, onMounted } = Vue;
const { defineStore, createPinia } = Pinia;
const usePokemons = defineStore('pokemon', {
state: () => ({ pokemons: [] })
});
const pinia = createPinia();
createApp({
pinia,
setup() {
const store = usePokemons(pinia);
const state = reactive({
searchTerm: '',
filteredPokemons: computed(() => store.pokemons.filter(
pokemon => pokemon.name.includes(state.searchTerm)
))
});
onMounted(() => {
fetch('https://pokeapi.co/api/v2/pokemon?offset=0')
.then(r => r.json())
.then(r => store.pokemons = r.results)
});
return {
...toRefs(state)
}
}
}).mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vue-demi"></script>
<script src="https://unpkg.com/pinia#2.0.11/dist/pinia.iife.prod.js"></script>
<div id="app">
<input v-model="searchTerm">
<div v-for="pokemon in filteredPokemons">
{{ pokemon.name }}
</div>
</div>

Related

Using Quasar q-select with a filter enabled when options is a json object

I cannot find any examples using composition api for this and could use some direction. I have a q-select which passes options as a prop using a axios request. The data is in this form:
[{description: "Apple Inc.", displaySymbol: "AAPL"}, {description: "Microsoft", displaySymbol: "MSFT"}]
I have about 20000 records in this JSON response. I am able to display it all in a v-select using:
<q-select
class="grey-7"
filled
v-model="addStockSymbol"
use-input
input-debounce="0"
label="Add New Stock Symbol"
:options="stockTickers"
option-label="description"
option-value="displaySymbol"
#blur="addPosition"
#filter="filterFn"
behavior="menu"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
My issue is I do not know how to setup the filter and update function so I can search this. So far I have the code below but the examples on quasar do not use any arrays with objects but rather simple arrays. So I am wondering how do I approach this?
<script>
import {watch, ref, defineComponent,onMounted} from 'vue'
import {usePortfolioStore} from '../stores/portfolio-store'
import {storeToRefs} from 'pinia'
import {finnhubAPI} from 'boot/axios'
export default defineComponent({
name: 'UploadPositions',
components: {
},
setup () {
//v-models
const addStockSymbol = ref('')
const addShareCount = ref('')
const stockTickers = ref([])
const loadData = () => {
finnhubAPI.get('/api/v1/stock/symbol?exchange=US&token=tedkfjdkfdfd')
.then((response) => {
stockTickers.value = response.data
})
.catch(() => {
console.log('API request failed')
})
}
const filterFn = (val, update) => {
if (val === '') {
update(() => {
stockTickers.value =
})
return
}
}
update(() => {
const needle = val.toLowerCase()
this.options = stringOptions.filter(v => v.toLowerCase().indexOf(needle) > -1)
})
//add on mount API request
onMounted(() => {
loadData()
})
return {
addStockSymbol, addShareCount, portfolio, addPosition, deletePosition,
loadData, stockTickers, modifyTickerData, filterFn, update
}
}
})
</script>
Basically you need to store a complete copy of the response data and keep that around, untouched, so that each time the filter function is called you can filter off of that, looking within its objects for the label prop.
When setting up refs:
//v-models
const addStockSymbol = ref('')
const addShareCount = ref('')
const stockTickers = ref([])
const allResponseData= ref([]) // <-- add this one
Then your loadData function:
const loadData = () => {
finnhubAPI.get('/api/v1/stock/symbol?exchange=US&token=cc8ffgiad3iciiq4brf0')
.then((response) => {
const responseData = response.data.map((item) => ({label: item.description, value: item.displaySymbol}));
allResponseData.value = [...responseData];
stockTickers.value = [...responseData];
})
.catch(() => {
console.log('API request failed')
})
}
Then in your filter function:
const filterFn = (val, update, abort) => {
update(() => {
const needle = val.toLowerCase()
stockTickers.value = allResponseData.value.filter(option => {
return option.label.toLowerCase().indexOf(needle) > -1
})
})
}
See it in action:
const { ref } = Vue
const stringOptions = [
{label: 'Google', value: "goog"}, {label:'Facebook',value:'fb'}, {label:'Twitter', value: "twit"},{label: 'Apple', value: 'App'}]
const app = Vue.createApp({
setup () {
const options = ref(stringOptions)
return {
model: ref(null),
options,
filterFn (val, update, abort) {
update(() => {
const needle = val.toLowerCase()
options.value = stringOptions.filter(option => {
return option.label.toLowerCase().indexOf(needle) > -1
})
})
}
}
}
})
app.use(Quasar, { config: {} })
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/quasar#2.7.7/dist/quasar.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.7.7/dist/quasar.umd.prod.js"></script>
<!--
Forked from:
https://quasar.dev/vue-components/select#example--basic-filtering
-->
<div id="q-app" style="min-height: 100vh;">
<div class="q-pa-md">
<div class="q-gutter-md row">
<q-select
filled
v-model="model"
use-input
hide-selected
fill-input
input-debounce="0"
:options="options"
#filter="filterFn"
hint="Basic filtering"
style="width: 250px; padding-bottom: 32px"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
</div>
</div>
</div>

Why my vue components don't update when the state in pinia changes?

I'm working on a website that uses vue and firebase. After the authentication part the user gets to the dashboard where there is the list of his projects that are a subcollection of the user document in firestore.
I made a pinia store that manages this data and each time a project is created with the form or gets deleted the state.projects updates with the new array of projects that gets cycled to display the list in the view.
Inside the view I have access to the store.projects thanks to a getter that should be reactive but when I add or delete a project nothing happens in the view, but still the state.projects gets updated.
here is the code of the DashboardView.vue:
<template>
<MainHeader mode="dashboard" />
<main class="main">
<div class="main__container">
<section class="main__section">
<div class="section__header">
<h1 class="header__title">Projects</h1>
<!-- <TextInput type="text" placeholder="Search" v-model="filter" /> -->
</div>
<div class="section__content">
<ul class="content__list">
<li
v-for="project in projects"
:key="project.id"
class="content__item"
>
{{ project.id }}
<!-- <router-link
:to="{ name: 'ProjectView', params: { id: project.id} }">
</router-link> -->
<SimpleButton #click="deleteProject(project.id)" type="button" text="delete" />
</li>
</ul>
</div>
<div class="section__footer">
<form #submit.prevent="createProject">
<TextInput type="text" placeholder="name" v-model="form.id" />
<TextInput type="text" placeholder="website" v-model="form.website" />
<SimpleButton type="submit" text="Add" />
</form>
</div>
</section>
</div>
</main>
</template>
<script>
import { useUserDataStore } from "../stores/UserDataStore.js";
import MainHeader from "../components/MainHeader.vue";
import SimpleButton from "../components/SimpleButton.vue";
import TextInput from "../components/TextInput.vue";
import { ref } from '#vue/reactivity';
export default {
name: "DashboardView",
components: {
MainHeader,
SimpleButton,
TextInput,
},
setup() {
// const filter = "";
const form = ref({});
const userDataStore = useUserDataStore();
const projects = userDataStore.getProjects;
const createProject = () => {
userDataStore.createProject(form.value)
}
const deleteProject = (id) => {
userDataStore.deleteProject(id)
}
return {
projects,
form,
createProject,
deleteProject,
};
},
};
</script>
And here the pinia store code:
import { defineStore } from "pinia";
import router from "../router";
import { db } from '../firebase';
import { doc, setDoc, getDoc, getDocs, collection, deleteDoc } from 'firebase/firestore'
export const useUserDataStore = defineStore('UserDataStore', {
state: () => {
userData: { }
projects: []
uid: null
},
actions: {
createNewUser(uid, name) {
setDoc(doc(db, "users", uid), {
name
})
.then(() => {
this.fetchUserData(uid)
})
.catch((error) => console.log(error))
},
fetchUserData(uid) {
this.uid = uid
// Fetch user doc with uid
getDoc(doc(db, "users", uid))
.then((response) => {
this.userData = response.data()
// Fetch user projects
getDocs(collection(db, "users", uid, "projects"))
.then((response) => {
const projectsArray = []
response.forEach(el => {
projectsArray.push({ data: el.data(), id: el.id})
})
this.projects = projectsArray
console.log(this.projects);
router.push({ name: 'DashboardView' })
})
})
.catch((error) => console.log(error))
},
createProject(details) {
const { id, website } = details
setDoc(doc(db, "users", this.uid, "projects", id), {
website
}).then(() => {
console.log('created');
this.fetchUserData(this.uid)
})
.catch((err) => console.log(err))
},
deleteProject(id) {
deleteDoc(doc(db, "users", this.uid, "projects", id))
.then(() => {
console.log('deleted');
this.fetchUserData(this.uid);
})
.catch(err => console.log(err))
}
},
getters: {
getProjects: (state) => state.projects
}
})
A store is reactive object, the reactivity of store property is disabled at the time when it's accessed in setup function:
const projects = userDataStore.getProjects;
It should be either:
const projects = computed(() => userDataStore.getProjects);
Or:
const { getProjects: projects } = storeToRefs(userDataStore);

Vuejs & Auth0 : I need to reload page to be Authenticated

I'm a beginner in Vue, and I implemented Auth0 to my Web App using Vue3.
My issue: after logging in, my API call to retrieve data get an unauthorized error 403. If I reload the page, everything is working fine.
What should I do to avoid reloading the page to get authenticated directly?
Here are my scripts:
Main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './index.css'
import dayjs from 'dayjs'
import Datepicker from 'vue3-date-time-picker'
import 'vue3-date-time-picker/dist/main.css'
import { setupAuth } from './auth/index.js'
import authConfig from './auth/config.js'
function callbackRedirect(appState) {
router.push(appState && appState.targetUrl ? appState.targetUrl : '/' );
}
setupAuth(authConfig, callbackRedirect).then((auth) => {
let app = createApp(App).use(router);
app.config.globalProperties.$dayjs = dayjs;
app.component('Datepicker', Datepicker);
app.use(auth).mount('#app');
})
My App.vue script:
<template>
<div v-if="isAuthenticated">
<NavBar />
<router-view/>
</div>
</template>
<script>
import NavBar from './components/NavBar.vue'
export default {
components: { NavBar },
data(){
return {
isAuthenticated: false,
}
},
async mounted(){
await this.getAccessToken()
},
methods: {
async getAccessToken(){
try {
const accessToken = await this.$auth.getTokenSilently()
localStorage.setItem('accessToken', accessToken)
this.isAuthenticated = true
} catch (error) {
console.log('Error occured while trying to retrieve Access Token...', error)
}
},
},
}
</script>
and my Home.vue loading the data:
<template>
<div class="home">
<div class="py-10">
<header>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold leading-tight text-gray-900">Monitoring Dashboard</h1>
</div>
</header>
<main>
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<h3 class="m-5 text-lg leading-6 font-medium text-gray-900">Main KPIs</h3>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div v-for="(item, index) in stats" :key="index" class="md:col-span-1">
<div class="bg-white p-5 border-gray-50 rounded-lg shadow-lg mb-5">
<span class="text-sm font-medium text-gray-500 truncate">{{ item.name }}</span>
<p class="mt-1 text-3xl font-bold text-gray-900">{{ parseFloat(item.stat.toFixed(2)) }}</p>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import ProductsService from '../services/products.service'
export default {
name: 'Home',
data(){
return{
user: '',
products: '',
stats: '',
}
},
async mounted(){
await this.readProducts()
await this.buildStats()
},
methods: {
async readProducts(){
let temp = null
try {
temp = await ProductsService.readProducts()
this.products = temp.data
} catch (error) {
console.log('Error: cannot retrieve all products...')
}
},
async buildStats(){
//Nb products
const nbProducts = this.products.length
//Nb offers & Uniq NbRetailers
let nbOffers = 0
let retailers = []
for(let product of this.products){
for(let offer of product.offers){
retailers.push(offer.retailer)
nbOffers += 1
}
}
const nbRetailers = _.uniq(retailers).length
this.stats = [
{ name: 'Number of Retailers', stat: nbRetailers },
{ name: 'Number of Products', stat: nbProducts },
{ name: 'Number of Offers', stat: nbOffers },
]
},
},
watch: {
products: function(){
this.buildStats()
}
}
}
</script>
My ./auth/index.js file:
import createAuth0Client from '#auth0/auth0-spa-js'
import { computed, reactive, watchEffect } from 'vue'
let client
const state = reactive({
loading: true,
isAuthenticated: false,
user: {},
popupOpen: false,
error: null,
})
async function loginWithPopup() {
state.popupOpen = true
try {
await client.loginWithPopup(0)
} catch (e) {
console.error(e)
} finally {
state.popupOpen = false
}
state.user = await client.getUser()
state.isAuthenticated = true
}
async function handleRedirectCallback() {
state.loading = true
try {
await client.handleRedirectCallback()
state.user = await client.getUser()
state.isAuthenticated = true
} catch (e) {
state.error = e
} finally {
state.loading = false
}
}
function loginWithRedirect(o) {
return client.loginWithRedirect(o)
}
function getIdTokenClaims(o) {
return client.getIdTokenClaims(o)
}
function getTokenSilently(o) {
return client.getTokenSilently(o)
}
function getTokenWithPopup(o) {
return client.getTokenWithPopup(o)
}
function logout(o) {
return client.logout(o)
}
export const authPlugin = {
isAuthenticated: computed(() => state.isAuthenticated),
loading: computed(() => state.loading),
user: computed(() => state.user),
getIdTokenClaims,
getTokenSilently,
getTokenWithPopup,
handleRedirectCallback,
loginWithRedirect,
loginWithPopup,
logout,
}
export const routeGuard = (to, from, next) => {
const { isAuthenticated, loading, loginWithRedirect } = authPlugin
const verify = () => {
// If the user is authenticated, continue with the route
if (isAuthenticated.value) {
return next()
}
// Otherwise, log in
loginWithRedirect({ appState: { targetUrl: to.fullPath } })
}
// If loading has already finished, check our auth state using `fn()`
if (!loading.value) {
return verify()
}
// Watch for the loading property to change before we check isAuthenticated
watchEffect(() => {
if (loading.value === false) {
return verify()
}
})
}
export const setupAuth = async (options, callbackRedirect) => {
client = await createAuth0Client({
...options,
})
try {
// If the user is returning to the app after authentication
if (
window.location.search.includes('code=') &&
window.location.search.includes('state=')
) {
// handle the redirect and retrieve tokens
const { appState } = await client.handleRedirectCallback()
// Notify subscribers that the redirect callback has happened, passing the appState
// (useful for retrieving any pre-authentication state)
callbackRedirect(appState)
}
} catch (e) {
state.error = e
} finally {
// Initialize our internal authentication state
state.isAuthenticated = await client.isAuthenticated()
state.user = await client.getUser()
state.loading = false
}
return {
install: (app) => {
app.config.globalProperties.$auth = authPlugin
},
}
}

Why content of child component with props is not rendered on the page?

In vuejs3 app
I read data with axios request from backend API. I see that data are passed to internal
component, but I do not see content of the child component is rendered on the page.
Parent component:
<template>
<div class="row m-0 p-0" v-show="forumCategories.length && isPageLoaded">
<div v-for="(nextActiveForumCategory, index) in forumCategories" :key="nextActiveForumCategory.id" class="col-sm-12 col-md-6 p-2 m-0">
index::{{ index}}
<forum-category-block
:currentLoggedUser="currentLoggedUser"
:nextActiveForumCategory="nextActiveForumCategory"
:index="index"
:is_show_location="true"
></forum-category-block>
</div>
</div>
</template>
<script>
import ForumCategoryBlock from '#/views/forum/ForumCategoryBlock.vue'
import { useStore } from 'vuex'
export default {
name: 'forumsByCategoryPage',
components: {
ForumCategoryBlock,
},
setup () {
const store = useStore()
const orderBy = ref('created_at')
const orderDirection = ref('desc')
const forumsPerPage = ref(20)
const currentPage = ref(1)
let forumsTotalCount = ref(0)
let forumCategories = ref([])
let isPageLoaded = ref(false)
let credentialsConfig = settingCredentialsConfig
const currentLoggedUserToken = computed(
() => {
return store.getters.token
}
)
const currentLoggedUser = computed(
() => {
return store.getters.user
}
)
const forumsByCategoryPageInit = async () => {
loadForums()
}
function loadForums() {
isPageLoaded = false
let credentials = getClone(credentialsConfig)
credentials.headers.Authorization = 'Bearer ' + currentLoggedUserToken.value
let filters = { current_page: currentPage.value, order_by: orderBy.value, order_direction: orderDirection.value }
const apiUrl = process.env.VUE_APP_API_URL
axios.get(apiUrl + '/forums-by-category', filters, credentials)
.then(({ data }) => {
console.log('/forums-by-category data::')
console.log(data)
forumCategories.value = data.forumCategories
forumsTotalCount.value = data.forumsTotalCount
isPageLoaded = true
console.log('++forumCategories::')
console.log(forumCategories)
})
.catch(error => {
console.error(error)
isPageLoaded = true
})
} // loadForums() {
onMounted(forumsByCategoryPageInit)
return {
currentPage, orderBy, orderDirection, isPageLoaded, loadForums, forumCategories, getHeaderIcon, pluralize, forumsTotalCount, forumCategoriesTitle, currentLoggedUser
}
} // setup
</script>
and ForumCategoryBlock.vue:
<template>
<div class="">
<h1>INSIDE</h1>
<fieldset class="bordered" >
<legend class="blocks">Block</legend>
nextActiveForumCategory::{{ nextActiveForumCategory}}<br>
currentLoggedUser::{{ currentLoggedUser}}<br>
index::{{ index }}<br>
</fieldset>
</div>
</template>
<script>
import { computed } from 'vue'
export default {
name: 'forumCategoryBlock',
props: {
currentLoggedUser: {
type: Object,
default: () => {}
},
nextActiveForumCategory: {
type: Object,
default: () => {}
},
index: {
type: Number,
default: () => {}
}
},
setup (props) {
console.log('setup props::')
console.log(props)
const nextActiveForumCategory = computed({
get: () => props.value.nextActiveForumCategory
})
const currentLoggedUser = computed({
get: () => props.value.currentLoggedUser
})
const index = computed({
get: () => props.index
})
return { /* currentLoggedUser, nextActiveForumCategory, index */ }
}
}
</script>
What I see in browser : https://prnt.sc/vh7db9
What is wrong abd how to fix it ?
MODIFIED :
I understood WHERE the error :
<div class="row m-0 p-0" v-show="forumCategories.length && isPageLoaded" style="border: 2px dotted red;">
if to remove 2nd condition && isPageLoaded in a line above I see content.
But looks like that var isPageLoaded is not reactive and I do not see why?
If is declared with ref and is declared in return of setup method.
But looks like as I modify it in loadForums method it does not work in template...
Thanks!
isPageLoaded is losing its reactivity because loadForums() is changing its type from ref to Boolean:
isPageLoaded = true // ❌ no longer a ref
isPageLoaded is a ref, so your code has to access it through its value property. It's probably best to use const instead of let here to avoid this mistake:
const isPageLoaded = ref(false)
isPageLoaded.value = true // ✅

Vue.js return data from axios

<template>
<div class="comment">
<div v-for="(comment, index) in comments" :key="index">
{{ getUser(comment.student_idx) }}
</div>
</div>
</template>
<script>
import axios from 'axios'
import server from '#/models/server'
export default {
data() {
return {
url: server,
comments: {}
}
},
props: ['idx'],
created() {
axios.get(`${this.url}/bamboo/reply?post=${this.idx}`)
.then(response => {
if (response.data.status === 200) {
this.comments = response.data.data.replies
}
})
},
methods: {
getUser (idx) {
axios.get(`${this.url}/member/student/${idx}`)
.then(response => {
if (response.data.status === 200) {
return response.data.data.member.name
}
})
}
}
}
</script>
I would like to load the comments at created and print them out using v-for.
In v-for, I would like to load the member.name from each comment
But {{ getUser(comment.student_idx) }} is undefined.
I don't know how to return data from axios
Help me please!!
Your method should not be async for stable run code. My recomendation is next code:
<template>
<div class="comment">
<div v-for="(comment, index) in comments" :key="index">
{{ comments['user'] }}
</div>
</div>
</template>
<script>
import axios from 'axios'
import server from '#/models/server'
export default {
data() {
return {
url: server,
comments: []
}
},
props: ['idx'],
created() {
axios.get(`${this.url}/bamboo/reply?post=${this.idx}`)
.then(response => {
if (response.data.status === 200) {
this.comments = response.data.data.replies;
if(this.comments)
for(let comment of this.comments){
this.getUser(comment, comment.student_idx);
}
}
})
},
methods: {
getUser (comment, idx) {
axios.get(`${this.url}/member/student/${idx}`)
.then(response => {
if (response.data.status === 200) {
this.$set(comment, 'user', response.data.data.member.name);
}
})
}
}
}
</script>