2

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>

1 Answer 1

2

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))
Sign up to request clarification or add additional context in comments.

1 Comment

Incredible. I've written an entire app without using computed() but this works! I guess I need to research what computed() does. Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.