1

Environment

"@strapi/strapi": "4.12.0",

"nuxt": "^3.6.5",

Current situation

In my development environment, I have a projects page with a category filter that can show either all projects or a category with the corresponding projects.

I am using useAsyncQuery to fetch the projects and categories from Strapi.

In the filter there is a dropdown with various categories. On click of those categories, the function switchCategory(filter.id) is called. In that function are various states updated using this .

See here the script setup:

<script setup> import { projectsQuery } from "~/queries/projects/archive"; import { filterQuery } from "~/queries/projects/filter"; const { data: filterRes } = await useAsyncQuery(filterQuery) const { data: projectsRes } = await useAsyncQuery(projectsQuery) var isLoading = useState('isLoading', () => false) var filterActive = useState('filterActive', () => false) var filterActiveText = useState('filterActiveText', () => 'Alle projecten') const projectsUrl = useState('projectsUrl', () => '/projecten/') const filters = useState('filters', () => filterRes.value.projectCategories.data) const projects = useState('projects', () => projectsRes.value.projects.data) var filteredProjects = useState('filteredProjects', () => projects) function switchCategory(id) { this.isLoading = true this.filterActive = false; document.getElementById("projects-container").scrollIntoView({ behavior: 'smooth', }); setTimeout(() => { if (id) { this.filters.map((item) => { if (id == item.id) { this.filteredProjects = item.attributes.projects.data; this.filterActiveText = item.attributes.Title; } }); } else { this.filteredProjects = this.projects; this.filterActiveText = 'Alle projecten'; } this.isLoading = false; }, 600) } 

And in the template element

<div class="flex flex-col gap-y-8 md:gap-y-24 lg:gap-y-40 transition-all duration-600 ease-in-out" :class="{ 'blur-0': !isLoading, 'blur': isLoading, }"> <div class="grid md:grid-cols-2 gap-8 md:gap-16 lg:gap-24 relative" v-for="(project, id) in filteredProjects" :key="id"> <Nuxt-Link class="absolute inset-0 w-full h-full z-10" :to="projectsUrl + project.attributes.Slug"></Nuxt-Link> <div class="flex flex-col items-start justify-between"> <div class="sticky top-40 pb-16 lg:pb-20 prose-xl"> <h3 class="text-xl md:text-4xl lg:text-5xl font-bold">{{ project.attributes.Title }}</h3> <p class="block">{{ project.attributes.Intro }}</p> </div> <Button class="flex mb-4" color="light" :to="projectsUrl + project.attributes.Slug"> Project bekijken </Button> </div> <div class="-order-1 md:order-1 relative"> <div class="aspect-video md:aspect-square lg:aspect-[12/18] relative"> <Media :url="project.attributes.FeaturedImage.data.attributes.url" :extension="project.attributes.FeaturedImage.data.attributes.ext" /> </div> </div> </div> </div> 

Problem

When I build the project, using nuxt build and then start a node server using node .output/server/index.mjs the projects get loaded in fine. But when I try to filter, trying to update the variables like isLoading and filterActive inside the switchCategory function, it throws me: TypeError: Cannot set properties of undefined (setting 'isLoading').

Fix attempt #1

I thought, maybe when it's built, it doesn't understand the this context (because this is also undefined when I try to console.log it). So I tried to refactor the code like this:

const switchCategory = (id) => { isLoading = true filterActive = false; document.getElementById("projects-container").scrollIntoView({ behavior: 'smooth', }); setTimeout(() => { if (id && filters) { filters && filters.value.map((item) => { if (id == item.id) { filteredProjects = item.attributes.projects.data; filterActiveText = item.attributes.Title; } }); } else { filteredProjects = projects; filterActiveText = 'Alle projecten'; } isLoading = false; }, 600) } 

I changed the normal function to an arrow function and removed the this.
In the setTimeout I had to change the filters.map to filters.value.map (not entirely sure why it changed format).

Code runs normal now (without errors), but it doesn't change update the DOM of the projects loop in the template.

Fix attempt #2

After searching and debugging for a pretty long time, I found in the Vue docs about defineExpose.
It mentions that script setup is "closed by default". I (desperately) added it to my script setup with all variables:

defineExpose({ filterRes, projectsRes, pageRes, isLoading, filterActive, filterActiveText, projectsUrl, filters, projects, filteredProjects, }) 

Unfortunately, and as expected, it didn't magically work.

------

Solution?

I am most likely looking at this in the wrong way, but I can't think of any other way on how to fix this.
Am I missing something?

2 Answers 2

0

I dont think i can supply a straight working code snippet, but perhaps can guide you into a solution?

useState is only available in build time, or well, component render. So changing the query after render doesnt make it update. Perhaps re-render the component after state mutation?

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for commenting! So I've dived into trying to accomplish to rerender the component. It kinda "worked" when trying to filter for the first time, but after that it didn't work anymore. Unfortunately.
0

I think I (finally) fixed the problem.

So after trying sven folkerts guidance to a solution (thanks btw), using this article as one of the references and sleeping one night before continuing, I've went at this in a different direction.

I continued with the arrow function approach in Fix attempt #1.

I researched more about refreshNuxtData but didn't have much luck.

Then I went into into using ref instead of useState. That worked. Both in development and build.

See my final code here:

<script setup> import { pagesQuery } from "~/apollo/queries/pages"; import { projectsQuery } from "~/apollo/queries/projects/archive"; import { filterQuery } from "~/apollo/queries/projects/filter"; const { data: filterRes } = await useAsyncQuery(filterQuery) const { data: projectsRes } = await useAsyncQuery(projectsQuery) const { data: pageRes } = await useAsyncQuery(pagesQuery, { slug: 'projecten' }); var isLoading = ref(false) var filterActive = ref(false) var filterActiveText = ref('Alle projecten') const projectsUrl = '/projecten/' const filters = filterRes.value.projectCategories.data const projects = projectsRes.value.projects.data var filteredProjects = ref(projects) const switchCategory = (id) => { isLoading.value = true filterActive.value = false; document.getElementById("projects-container").scrollIntoView({ behavior: 'smooth', }); setTimeout(() => { if (id) { filters && filters.map((item) => { if (id == item.id) { filteredProjects.value = item.attributes.projects.data; filterActiveText.value = item.attributes.Title; } }); } else { filteredProjects.value = projects; filterActiveText.value = 'Alle projecten'; } isLoading.value = false; }, 600) } 

As you can see I changed all useState variables to ref (and changed all illogical useState declarations to normal declarations).

In my switchCategory function I only had to update all variable updates to update the value of the ref to var.value.

I'm happy it's working now, but I am still unsure if this is the ideal / most logical way.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.