mrichar1 / pinia-jsonapi

Use a JSONAPI api with a Pinia store, with data restructuring/normalization. Topics
GNU Affero General Public License v3.0
8 stars 2 forks source link

Computed model is not reactive #5

Closed mdrews21 closed 2 months ago

mdrews21 commented 2 months ago

I neeed your Advice on following issue: I don't get a reactive state from model in pinia-jsonapi store. Every time I patch the model the store is updated, but the computed property in my component isn't.

E.g. The tasks are fetched in parent component with

const {pending, data} = await useAsyncData(`user-tasks`, async () => {
    const params = {}
    return await store.get('users/1/tasks')
})

The store looks like that afterwards: grafik

Changing the checkbox value in child component triggers a store.patch() request. The pinia-jsonapi store is updated with the updated model. But the computed property const task = computed() still has the old attributes.

Can you please give me an advice what I am doing wrong?

Here is my :

<script setup>
const props = defineProps({
    id: {
        type: Number,
        required: true
    },
})

const id = toRef(props, "id")

const store = useJsonapi().jsonapiStore()

const task = computed(() => {
    return store.getData({_jv: {type: "tasks", id: id.value} })
})

const is_complete = computed({
    get() {
        return task.value?.is_complete
    },
    async set(val) {
        const item = {
            is_complete: val,
            _jv: {
                type: "tasks",
                id: id.value,
            },
        }
        store.patch(item, {params: null})
    },
})
</script>

<template>
    <v-list-item
        density="compact"
        :class="{ 'task-completed': is_complete }">
        <template #prepend>
            <v-progress-circular
                v-if="isLoading"
                :size="23"
                color="#425e5e"
                indeterminate />
            <v-checkbox
                v-else
                v-model="is_complete"
                hide-details />
        </template>
        <NuxtLink
            v-slot="{ navigate }"
            :prefetch="false"
            :to="{ name: task?.link?.route, params: task?.link?.params, query: task?.link?.query }">
            <v-list-item-title
                :class="task?.route ? 'cursor-pointer' : null"
                @click.stop="navigate">
                <div v-html="task?.description" />
            </v-list-item-title>
        </NuxtLink>
    </v-list-item>
</template>

Composable <useJsonapi()>:

import {createJsonapiStore} from "pinia-jsonapi"
import {useOFetchApi} from "~/composables/useOFetchApi.js"

export function useJsonapi() {
    // using fetch api to prevent using axios cause axios is not natively supported in Nuxt 3 anymore
    const api = useOFetchApi()
    const conf = {
        jvtag: "_jv",
        preserveJson: true,
    }
    const {jsonapiStore, status, utils} = createJsonapiStore(api, conf)
    return {jsonapiStore, status, utils}
}

Composable <useOFetchApi()>:

import {ofetch} from "ofetch"
import {useUiStore} from "~/stores/ui"
import {useLogs} from "~/composables/useLogs"

export function useOFetchApi() {
    /**
     * use oFetch to support pinia-jsonapi
     * https://github.com/mrichar1/pinia-jsonapi/issues/1
     */
    const {log} = useLogs("useOFetchApi")
    const config = useRuntimeConfig()
    const baseURL = `${config.public.API_PROTOCOL}://${config.public.API_HOST}:${config.public.API_PORT}${config.public.API_PREFIX}api/${config.public.API_VERSION}`
    // 'request' contains a subset of axios configuration properties: https://axios-http.com/docs/req_config
    return async request => {
        // Construct the URL
        const url = request.url
        // Construct the ofetch config
        const uiStore = useUiStore()
        const oCfg = {
            baseURL,
            headers: {
                "Content-Type": "application/vnd.api+json",
                "Accept": "application/json, application/vnd.api+json, text/plain"
            },
            method: request.method,
            query: request.params,
            parseResponse: JSON.parse,
            credentials: "include",
            async onRequest({request, options}) {
                log.debug(() => console.log("➡️ REQUEST - [BEFORE manipulating fetch request from useOFetchApi] options:", options))
                // Log request
                const token = useCookie("XSRF-TOKEN").value ?? false
                if (token) {
                    options.headers = {
                        ...options.headers,
                        "X-XSRF-TOKEN": token
                    }
                }
                if (
                    options.method === "GET"
                    || options.method === "get"
                ) {
                    // modifies Content-Type on GET Requests to avoid options preflight in browser
                    options.headers = {
                        ...options.headers,
                        "Content-Type": "text/plain"
                    }
                }
                log.debug(() => console.log("➡️ REQUEST - [AFTER manipulating fetch request from useOFetchApi] options:", options))
                // enable loading state in pinia Store
                uiStore.$patch(state => {
                    state.topmenu.isLoading = true
                    state.hasChanged = true
                })
            },
            async onResponse({request, response, options}) {
                // Log response
                log.info(() => console.log("⬅️ RESPONSE - [fetch response from useOFetchApi]"))
                // disable loading state in pinia Store
                uiStore.$patch(state => {
                    state.topmenu.isLoading = false
                    state.hasChanged = true
                })
            }
        }
        if (request.method === "post" || request.method === "POST" || request.method === "patch" || request.method === "PATCH") {
            // Add body if data is defined (POST/PATCH)
            if ("data" in request)
            {oCfg.body = request.data}
            log.info(() => console.log(`✅ [added body to request in useOFetchApi because its a ${request.method} request]`))
        }
        const result = await ofetch.raw(url, oCfg)
        // axios returns JSON nested under 'data' property.
        return {data: result._data}
    }
}
mrichar1 commented 2 months ago

I can't immediately see any issues with this code that would explain the lack of reactivity. getData is a 'standard' pinia getter, so should behave reactively (equivalent to a component).

This code is effectively what happens in the testapp in examples for comparison. If you want to play with that, check out this repo and run npm run dev then play with the Patch fields in the form.

One possibility is that your patch is making a deep change that isn't being spotted by the reactivity system, so you may need to try a deep watcher to see if that's why it's being missed.

The only other debugging I can think to suggest is that since getData is a getter, you should be able to use it 'directly'. So you can use e.g. <pre>{{ store.getData({_jv: {type: "tasks", id: id} }) }}</pre> somewhere in your template to see changes.

mdrews21 commented 2 months ago

Im sorry for my late response. I tested with the example and found out, that there must be an issue using the ofetch Library shipped with Nuxt 3. Switched back to axios and reactivity is back... I don't know why but it worked