hope you're well!
I have a Vue 3 app using Pinia + Vuetify 3. I've defined a "client" store and a component that, upon render, will call a store action that calls my backend API and sets my client state (JSON) with the result.
clientStore.js:
export const useClientStore = defineStore('clients', {
state: () => ({
//Loading state and client(s)
loading: false,
clients: [],
client: {}
}),
getters: {
//Get all clients
getClients(state) {
return state.clients
},
//Get one client
getClient(state) {
return state.client
}
},
actions: {
//Get one client
async fetchClient(clientId) {
try {
this.loading = true
const data = await axiosConfig.get('/clients/' + clientId)
this.client = data.data
this.loading = false
} catch (error) {
this.loading = false
console.log("Error fetching client: " + clientId)
},
//snipped
I have a computed property that returns the client from the store and render them as follows:
Component.vue:
<template>
<div class="text-center py-5">
<div class="text-h4 font-weight-bold">{{ client.name }}</div>
</div>
<div class="d-flex justify-space-between">
<div class="text-h5">Description</div>
<v-btn @click="dialog = true" prepend-icon="mdi-cog" color="primary">Edit</v-btn>
</div>
<v-textarea class="py-5" :value="client.description" readonly auto-grow outlined>{{ client.description
}}</v-textarea>
<updateClient v-model="dialog" />
</template>
<script setup>
import updateClient from '@/components/clients/updateClient.vue'
import { useClientStore } from '@/store/clients'
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'
const store = useClientStore()
const route = useRoute()
const dialog = ref(false)
const client = computed(() => {
return store.client
})
onMounted(() => {
store.fetchClient(route.params.clientId)
})
</script>
My aim is to make an "EDIT" component - a popup dialog - that takes the client state values and pre-populate them in my text fields and upon changing the values, submit and PATCH the client in the backend.
updateClient.vue
<template>
<v-dialog max-width="500">
<v-card class="pa-5">
<v-card-title>Edit client</v-card-title>
<v-text-field label="Name" v-model="client.name"></v-text-field>
<v-textarea label="Description" v-model="client.description"></v-textarea>
<v-btn block outlined color="primary" @click="updateClient">Update Client</v-btn>
</v-card>
</v-dialog>
</template>
<script setup>
import { useClientStore } from '@/store/clients'
import {computed} from 'vue'
const store = useClientStore()
const client = computed(() => {
return store.client
})
</script>
Problem is when I edit the pre-populated values in the fields, it changes the values outside the dialog as seen in the video and stay changed even after closing the pop-up. Ideally I'd like the values in my Component.vue to be static and have my state values unaltered. How can this be solved?
Thanks!
When you bind client.name to a text field in "Edit component", you directly change values stored in pinia. This, by design, changes values in your "View component".
A simple answer is... just create a copy of the object.
Now, I know, I know... there is a reason why you used computed properties in both places. Because you're waiting on the server to return the initial values.
The easiest way to solve this is to create a copy of the client object in pinia store. Then, just use copy of the object for text field binding in "Edit component".
state: () => ({
//Loading state and client(s)
loading: false,
clients: [],
client: {},
clientEdit: {} // Make changes to this object instead
})
In api response
actions: {
//Get one client
async fetchClient(clientId) {
try {
this.loading = true
const data = await axiosConfig.get('/clients/' + clientId)
this.client = data.data
this.clientEdit = { ...this.client } // Copy client object
this.loading = false
} catch (error) {
this.loading = false
console.log("Error fetching client: " + clientId)
},
}