Todd Hammer
Todd Hammer

Reputation: 311

Vue 3 Component losing reactivity

I've been learning Vue 3 for the past month or so and have gotten quite far but I can't fix this no matter what I've tried. I know I'm losing reactivity but I can't figure out how and it's driving me nuts. I am using the Composition API and script setup with a simple Pinia store. I created a github repo for it here: https://github.com/thammer67/vue3-reactivity-problem

I have a view (ProjectsView.vue) of project elements that loops through a pinia store array of projects using v-for and passing the array object as a prop. ProjectsView.vue uses a hidden form component (ProjectForm.vue) that I use for adding new projects. Each project in the loop is another component (ProjectItem.vue) with a click handler to a route that loads ProjectDetail.vue. ProjectDetail.vue has a click handler that also uses ProjectForm.vue for editing the item.

Everything works great. I can add new projects, edit projects but when I edit a project the pinia store updates (I can see this in the Vue Dev tools) but the UI doesn't update untill I go back to the project list. I need to update the value in ProjectDetail.vue after saving. Here are the pertinent files.

ProjectDetail.vue:

<script setup>
import { useProjectStore } from '../stores/ProjectStore'
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import ProjectForm from '@/components/Form/ProjectForm.vue'

const projectStore = useProjectStore()
const route = useRoute()
const id = route.params.id
const project = projectStore.getProjectById(id)

const showEditProject = ref(false)
const editing = ref(false)

const editProject = (id)=> {
    editing.value = id
    showEditProject.value = true
}
</script>

<template>
    <div class="main">
        <div v-if="project" :project="project">
            <h2>Project Details</h2>
            <div>
                <div class="project-name">{{ project.project }}</div> 
            </div>
            <div style="margin-top: 1em">
                <button type="button" @click="editProject(project.id)">Edit</button>
            </div>

            <ProjectForm
                @hideForm="showEditProject=false" 
                :project="project"
                :editing="editing"
                :showAddEntry="showEditProject" />
        </div>
    </div>
</template>

ProjectForm.vue:

<script setup>
import { ref, toRef, reactive } from "vue"
import { useProjectStore } from '@/stores/ProjectStore.js'
import Input from './Input.vue'

const projectStore = useProjectStore() 
const showAddType = ref(false)

//Capture 'showAddEntry' prop from parent component
const props = defineProps(['showAddEntry', 'editing', 'project'])

//Copy prop values for the form
const projName = toRef(props.project.project)
const projId = toRef(props.project.id)

//new/edited values are stored on this reactive object
const formState = reactive({
    invalid: false,
    errMsg:  ""
})

const saveProject = () => {
    formState.invalid = false

    if(projId.value) {
        console.log(`Update existing project ${projId.value}`)

        projectStore.updateProject({
            id: projId.value,
            project: projName.value
        })
        .then(()=> {
            console.log("save was successful!")
            showAddType.value = false
            formState.invalid = false
            formState.errMsg = ""
            emit('hideForm')
        })
        .catch(err=>console.log("Error: ", err))
    } else {
        console.log(`Create new project`)
        //New Project
        projectStore.createProject({
            project: projName.value,
        })
        .then(()=> {
            showAddType.value = false
            formState.invalid = false
            formState.errMsg = ""
            emit('hideForm')
        })
    }
}

const hideForm = ()=> {
    formState.invalid = false
    showAddType.value=false
    emit('hideForm')
}

//Define emit event up to the parent that hides the form
const emit = defineEmits(['hideForm'])

</script>

<template>
    <div class="addform" :class="{ show: props.showAddEntry }">
        <h1 v-if="editing" class="title">Edit Project</h1>
        <h1 v-else class="title">Add New Project</h1>

        <div class="input-wrap" :class="{ 'input-err' : formState.invalid }">
            <Input 
                @input="projName = $event.target.value"
                type="text" 
                placeholder="Enter project name" 
                :value="projName"
            />
           
            <div class="entry-submit">
                <button v-if="editing" @click="saveProject">Save</button>
                <button v-else @click="saveProject">Create Project</button>
                <button @click="hideForm">Cancel</button>
            </div>
        </div>
        <p v-show="formState.invalid" class="err-msg">{{ formState.errMsg }}</p>
    </div>
</template>

Upvotes: 2

Views: 1461

Answers (1)

yoduh
yoduh

Reputation: 14639

project in ProjectDetails.vue is not aware of changes being made to it in the store. It will if you wrap it with computed()

import { computed } from 'vue'

const project = computed(() => projectStore.getProjectById(id))

Upvotes: 1

Related Questions