Search code examples
data-bindingvuejs3piniavue-reactivityvuetifyjs3

How to populate text-fields from pinia store state without changing the rendered values from other components?


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!


Solution

  • 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)
            },
        }