How to bind two Quill Editor instances to different v-models? - vue.js

I want to render two quill editor elements into one vue component. The editors are supposed to have their own v-model attached to them, so they can send data to different databases. I want to differentiate between both editor elements via description prop.
component.vue:
<p>Description:</p>
<text-editor description="true"/>
<p>Content:</p>
<text-editor/>
So if description="true" a v-if directive is triggered and renders the corresponding editor element.
text-editor.vue:
<template>
<div id="text-editor" class="text-editor">
<quill-editor v-if="descr" ref="quillDescr" :modules="modules" :toolbar="toolbar" v-model:content="descr" contentType="html"/>
<quill-editor v-else ref="quill" :modules="modules" :toolbar="toolbar" v-model:content="content" contentType="html"/>
</div>
</template>
<script setup>
import BlotFormatter from 'quill-blot-formatter'
import store from "../../../js/store";
import {watch, ref, nextTick, defineProps} from 'vue'
import {Quill} from "#vueup/vue-quill";
const props = defineProps({
description: false
})
const content = ref('')
const descr = ref('')
const quill = ref(null)
const quillDescr = ref(null)
store.re.body = ''
store.re.descr = ''
let newContent = ''
let newDescr = ''
Quill.debug('error')
watch(content, newValue => {
newContent = newValue
store.re.body = newValue
})
watch(descr, newValueDescr => {
newDescr = newValueDescr
store.re.descr = newValueDescr
})
watch(
() => store.re.body,
newValue => {
if (newContent === newValue) return
quill.value.setHTML(newValue)
// Workaround https://github.com/vueup/vue-quill/issues/52
// move cursor to typing position
nextTick(() => {
let q = quill.value.getQuill()
q.setSelection(newValue.length, 0, 'api')
q.focus()
})
}
)
watch(
() => store.re.descr,
newValueDescr => {
if (newDescr === newValueDescr) return
quillDescr.value.setHTML(newValueDescr)
// Workaround https://github.com/vueup/vue-quill/issues/52
// move cursor to typing position
nextTick(() => {
let qd = quillDescr.value.getQuill()
qd.setSelection(newValueDescr.length, 0, 'api')
qd.focus()
})
}
)
const modules = {
module: BlotFormatter,
}
const toolbar = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ size: ['small', false, 'large', 'huge'] }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ align: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
['link', 'image', 'video'],
['clean'],
]
</script>
Being not very experienced with watchers, I've tried duplicating the corresponding v-model:content:
<quill-editor v-if="descr" ref="quillDescr" v-model:content="descr"/>
<quill-editor v-else ref="quill" v-model:content="content"/>
ref:
const quillDescr = ref(null)
store.re.descr = ''
let newDescr = ''
as well as watcher parameters:
watch(descr, newValueDescr => {
newDescr = newValueDescr
store.re.descr = newValueDescr
})
But when I type in one editor field, the other editor field is filled with the letters I'm typing as well:
How would I configure the watchers so that they only watch the corresponding editor input?

I suspect it has to do with the DOM manipulation done in quill.js, which conflicts with Vue's own management of the virtual DOM.
However, I was able to resolve the issue by implementing the following optimizations.
Optimizations
You can significantly reduce the duplication of code and have a single <quill-editor> by conditionally setting store.re.body/store.re.descr based on props.description:
<template>
<div id="text-editor" class="text-editor">
<quill-editor ref="quill" :modules="modules" :toolbar="toolbar" v-model:content="content" contentType="html" />
</div>
</template>
<script setup>
⋮
const props = defineProps({
description: Boolean,
})
👇
if (props.description) {
store.re.descr = ''
} else {
store.re.body = ''
}
watch(content, newValue => {
newContent = newValue
👇
if (props.description) {
store.re.descr = newValue
} else {
store.re.body = newValue
}
})
watch(
() => props.description ? store.re.descr : store.re.body, 👈
newValue => {⋯}
)
⋮
</script>
demo 1
But this solution does not scale well if you later need to have the editor update more store properties (e.g., store.re.title and store.re.subtitle). A better solution would be to make the editor update any given property via a v-model implementation:
Define a modelValue string prop (via defineProps).
Define an update:modelValue emit with a string value (via defineEmits).
In the watch that sets store.re.body/store.re.descr, emit the update:modelValue event with new value.
In the watch for store.re.body/store.re.descr, change the watch's source to be props.modelValue.
<script setup>
⋮
1️⃣
const props = defineProps({
modelValue: String,
})
2️⃣
const emit = defineEmits({
'update:modelValue': value => true,
})
watch(content, newValue => {
newContent = newValue
3️⃣
emit('update:modelValue', newValue)
})
watch(
() => props.modelValue, 4️⃣
newValue => {⋯}
)
⋮
</script>
Then in the parent component, use v-model to bind store.re.body/store.re.descr:
<text-editor v-model="store.re.descr" />
<text-editor v-model="store.re.body" />
For some reason, setting store.re.body and store.re.descr in the parent component does not update the v-model in this case, but you can workaround that by using a computed prop. To make the changes to store.re.body reactive in this example:
<template>
<text-editor v-model="body" />
</template>
<script setup>
import { computed } from 'vue'
const body = computed({
get: () => store.re.body,
set: v => store.re.body = v,
})
</script>
demo 2

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>

Vue2 composition API array property update not updating

I have this component that accepts an array as a property:
import {
defineComponent,
getCurrentInstance,
toRefs,
watch,
} from "#vue/composition-api";
import { RecommendationAnswer, RecommendationQuestion } from "#models";
import { useCalculateInitialCount } from "./calculate-count";
import { useGetAnsweredQuestions } from "./list-questions";
export default defineComponent({
name: "StepThree",
emits: ["onSelect"],
props: {
products: {
type: Array,
required: false,
default: () => [],
},
questions: {
type: Array,
required: false,
default: () => [],
},
},
setup(props) {
const instance = getCurrentInstance();
const { products, questions } = toRefs(props);
watch(
products,
(currentProducts: any[]) => {
if (!currentProducts) return;
const currentQuestions = <RecommendationQuestion[]>questions.value;
useCalculateInitialCount(currentProducts, currentQuestions);
},
{
immediate: true,
}
);
const selectAnswer = (answer: RecommendationAnswer) => {
answer.selected = !answer.selected;
questions.value.forEach((question: RecommendationQuestion) => {
question.selected = !!question.answers.find(
(item: RecommendationAnswer) => item.selected
);
});
const answeredQuestions = useGetAnsweredQuestions(
<RecommendationQuestion[]>questions.value
);
instance.proxy.$emit("onSelect", {
step: 3,
questions: answeredQuestions,
});
};
return { selectAnswer };
},
});
The watch is triggered whenever the products array changes (which happens outside of this component).
I can see that the watch fires and then the function useCalculateInitialCount fires, which updates the count property on an answer.
This is displayed in the template:
<v-col cols="6">
<base-fade-up class="row" :duration="0.1" tag="div">
<v-col
class="text-center"
cols="12"
v-for="question in questions.slice(
0,
Math.ceil(questions.length / 2)
)"
:key="question.id"
>
{{ question.title }}
<v-card
class="w-100"
outlined
#click="selectAnswer(answer)"
v-for="answer in question.answers"
:key="answer.id"
>
<v-card-text class="text-center">
{{ answer.title }} ({{ answer.count }})
</v-card-text>
</v-card>
</v-col>
</base-fade-up>
</v-col>
When the component loads, the watch fires and the counts are displayed correctly:
But when the products update, even though I see the changes in the console.log:
The template does not update.
Does anyone know how I can get around this?
I think it's because your array does not have a new item, so for the watcher is the same array with the same amount of items even if one of them has changed. I'm not sure why you have to watch a property but if you need to watch all the changes in the array you can try to make a copy of the array first and then watch that copied array
I figured a work around for this, by created a computed property instead of watching the products.
The entire code looks like this:
import {
computed,
defineComponent,
getCurrentInstance,
toRefs,
watch,
} from "#vue/composition-api";
import { RecommendationAnswer, RecommendationQuestion } from "#models";
import { useCalculateInitialCount } from "./calculate-count";
import { useGetAnsweredQuestions } from "./list-questions";
export default defineComponent({
name: "StepThree",
emits: ["onSelect"],
props: {
products: {
type: Array,
required: false,
default: () => [],
},
questions: {
type: Array,
required: false,
default: () => [],
},
},
setup(props) {
const instance = getCurrentInstance();
const { products, questions } = toRefs(props);
const questionsWithCount = computed(() => {
const currentProducts = <any[]>products.value;
const currentQuestions = [...(<RecommendationQuestion[]>questions.value)];
if (!currentProducts?.length || !currentQuestions?.length) return;
useCalculateInitialCount(currentProducts, currentQuestions);
return currentQuestions;
});
const selectAnswer = (answer: RecommendationAnswer) => {
answer.selected = !answer.selected;
questions.value.forEach((question: RecommendationQuestion) => {
question.selected = !!question.answers.find(
(item: RecommendationAnswer) => item.selected
);
});
const answeredQuestions = useGetAnsweredQuestions(
<RecommendationQuestion[]>questions.value
);
instance.proxy.$emit("onSelect", {
step: 3,
questions: answeredQuestions,
});
};
return { questionsWithCount, selectAnswer };
},
});
This fixed the issue, because in the template I use the questionsWithCount instead of the questions

how to reset one of data in pinia state?

I have a design in setting page,every one of them hava reset button, now i using pinia to be store library.
I kown $reset is reset the whole pinia state,so,how to reset one of data in pinia state?
The typical way I do this:
const defaultState = {
foo: 'bar'
}
export const useFoo = defineStore('foo', {
state: () => ({ ...defaultState }),
actions: {
reset() {
Object.assign(this, defaultState);
}
}
})
You get the initial state and a reset() action which resets whatever state has to the initial. Obviously, you can pick and choose what you put in defaultState.
If you only want to reset one particular state prop, without touching anything else, just assign the default value to it:
useFoo().foo = 'bar';
If you find it useful, you can also have a generic update, where you can assign multiple values to state in one call:
actions: {
update(payload) {
Object.assign(this, payload)
}
}
Use it like:
useFoo().update({
foo: 'bar',
// add more props if needed...
});
Last, but not least, lodash's pick can be used to pick and choose what gets reset, from the defaultState values, without having to specify the actual values:
import { pick } from 'lodash-es';
const defaultState = {
foo: 'bar',
boo: 'far'
};
export const useFoo = defineStore('foo', {
state: () => ({ ...defaultState }),
actions: {
reset(keys) {
Object.assign(this, keys?.length
? pick(defaultState, keys)
: defaultState // if no keys provided, reset all
);
}
}
})
use it like:
useFoo().reset(['foo']);
This only resets foo to 'bar', but doesn't touch current value of boo.
To reset both (using the action above):
useFoo().reset(['foo', 'boo']);
...or useFoo().reset() or useFoo().reset([]), both of which reset all the state, because the keys?.length condition is falsey.
Here's a working example:
const { createPinia, defineStore, storeToRefs } = Pinia;
const { createApp, reactive, toRefs } = Vue;
const defaultState = {
foo: "bar",
boo: "far",
};
const useStore = defineStore("foobar", {
state: () => ({ ...defaultState }),
actions: {
reset(keys) {
Object.assign(
this,
keys?.length ? _.pick(defaultState, keys) : defaultState
);
},
},
});
const pinia = createPinia();
const app = createApp({
setup() {
const store = useStore();
const localState = reactive({
resetFoo: false,
resetBoo: false,
});
const resetStore = () => store.reset(
[
localState.resetFoo ? "foo" : null,
localState.resetBoo ? "boo" : null,
].filter((o) => o)
);
return { ...storeToRefs(store), ...toRefs(localState), resetStore };
},
});
app.use(pinia);
app.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://cdnjs.cloudflare.com/ajax/libs/pinia/2.0.28/pinia.iife.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<div id="app">
<input v-model="foo" />
<input v-model="boo" />
<pre v-text="JSON.stringify({foo, boo}, null, 2)"></pre>
<hr>
<label>
<input type="checkbox" v-model="resetFoo" />ResetFoo</label>
<label>
<input type="checkbox" v-model="resetBoo" />ResetBoo</label>
<button #click="resetStore">Reset</button>
</div>
Above example doesn't reset one property to the default value when the property is already changed.
That's because the defaultState is reactive, you need to copy the defaultState so it's not reactive anymore.
import _pick from 'lodash.pick';
const defaultState = {
foo: 'bar',
};
export const useStore = defineStore('store', {
state: () => ({...defaultState}),
actions: {
reset(keys) {
Object.assign(this, keys?.length
? _pick(defaultState, keys)
: defaultState // if no keys provided, reset all
);
}
}
})
Use it like this
useStore().reset(['foo']);
This will now reset foo back to bar

Quill Editor won't display v-model in input field (Vue 3)

I want to display some html, fetched from a database inside of quill editor. The html seems to be fine (displayed in <p> paragraph) and is bound to quill editor via v-model but it just does not get displayed:
<template>
<div id="text-editor" class="text-editor">
<quill-editor :modules="modules" :toolbar="toolbar" v-model:content="store.re.body" contentType="html"/>
<p>{{store.re.body}}</p>
</div>
</template>
<script setup>
import BlotFormatter from 'quill-blot-formatter'
import store from "../../../js/store";
store.re.body = ''
const modules = {
module: BlotFormatter,
}
const toolbar = [
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'size': ['small', false, 'large', 'huge'] }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'align': [] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
['link', 'image', 'video'],
['clean']
];
</script>
This is where the data gets fetched from the database (inside another vue component):
axios.get('/get-blog', {
params: {
id: state.id
}
})
.then(res => {
state.body = store.re.body = res.data[0].body
})
.catch(err => {
state.error = true
setTimeout(() => state.error = false, 5000)
})
I am using store.re.body (reactive store) to transport it to quill editor.
store.js:
import {reactive} from "vue";
const re = reactive({})
export default {
re
}
Here you can see the editor page displayed, below with working <p> paragraph but the editor input stays empty:
The quill-editor component does not watch props.content (vueup/vue-quill#35). The watch was removed in an attempt to fix another bug, where the cursor would reset to the beginning of the line.
As a workaround, add your own watch on content, and call quill-editor's setHTML() with the new value. However, you'll need to move the cursor to the end of the line (using Quill's setSelection()) to workaround the bug mentioned above:
<template>
<quill-editor ref="quill" v-model:content="content" ⋯/>
</template>
<script setup>
import store from '#/store'
import { watch, ref, nextTick } from 'vue'
const content = ref('')
const quill = ref(null)
let newContent = ''
watch(content, newValue => {
newContent = newValue
store.re.body = newValue
})
watch(
() => store.re.body,
newValue => {
if (newContent === newValue) return
quill.value.setHTML(newValue)
// Workaround https://github.com/vueup/vue-quill/issues/52
// move cursor to end
nextTick(() => {
let q = quill.value.getQuill()
q.setSelection(newValue.length, 0, 'api')
q.focus()
})
}
)
⋮
</script>
<template>
<quill-editor ref="quill" ⋯/>
</template>
demo
you should try with these example bellow
<script>
import {QuillEditor} from "#vueup/vue-quill";
export default {
components: {
QuillEditor,
},
data(){
return {
someText:''
}
},
computed: {
editor() {
return this.$refs.quillEditor;
},
},
methods: {
getSetText() {
this.someText = "<div><p>this is some text</p> </div>";
this.editor.setHTML(this.someText);
},
},
}
</script>
<template><quill-editor ref="quillEditor" contentType="html"v-model:content="someText" theme="snow" ></quill-editor></template>

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 // ✅