Search code examples
vuejs2vue-componentvuexvuetify.jsvue-props

Force props to update in child component after Vuex update


I have a child component (<BaseProjectTable>) that is re-used throughout my site that contains a Vuetify <v-data-table> component. The headers and content items for the data table are passed into the the component as props, and the item data is mapped into the parent as a mapGetter from a Vuex store. The child component contains editing functionality for each row, and I'm using a mapAction to update the API and Vuex data from there, with the idea being that since my mapGetter is reactive, it should update the data and hence the data table display. However, this is not working; I can see via dev tools that state is updated just fine, but the child component display is not.

Here is the relevant portion of the child <BaseProjectTable> component:

<template>
  <div>
    <v-data-table
      show-expand
      :headers="headers"
      :items="filteredItems"
      :search="search"
      tem-key="sow"
      @click:row="rowClick"
      :hide-default-footer="disablePagination"
      dense
      :disable-pagination="disablePagination"
    >
    ...
    </v-data-table>

    ...
    export default {
    name: "BaseProjectTable",
    props: {
      headers: Array,
      items: Array,
      loggedInUser: Object,
      title: String,
      max2chars: v => v.length <= 2 || 'Input too long!',
      editDialog: false,
      showPracticeFilter: {
        default: true,
        type: Boolean
      },
      showSEFilter: {
        default: true,
        type: Boolean
      },
      showStatusFilter: {
        default: true,
        type: Boolean
      },
      projectType: {
        type: String,
        default: 'active'
      },
      disablePagination: {
        type: Boolean,
        default: true
      },
    },
  },
  methods: {
  ...mapActions('projects', {
        saveProject: 'saveProject',
    }), 
    save() {
      // Update API and store with new data.
      this.saveProject({
        projectType: this.projectType,
        projectData: this.editedItem
      })
  },
  computed: {
      statuses()  {
        return this.projectType === 'active' ? this.activeStatuses : this.oppStatuses;
      },
      filteredItems() {
        return this.items.filter(d => {
          return Object.keys(this.filters).every(f => {
            return this.filters[f].length < 1 || this.filters[f].includes(d[f])
          })
        })
      },
    }

and here is the relevant code from the parent component:

<v-card>
  <BaseProjectTable
    :headers="headers"
    :items="activeProjects"
    :loggedInUser="loggedInUser"
    title="Active Projects"
    :disablePagination=false
  ></BaseProjectTable>
</v-card>
...
export default {
  computed: {
    ...mapGetters('projects', {
      activeProjects: 'activeProjects',
      closedProjects: 'closedProjects',
      opportunities: 'opportunities'
    }),
  }
}

The save() method updates the data in my Vuex store that is referenced by the activeProjects map getter (I have verified in the Vue devtools that this is true). It also shows the items value in the component itself updated in the Components tab in the dev tools. Since using mapGetters makes the data reactive, I expected that it would also update the data in my child component, but it doesn't.

Based on what I saw here, I tried the item-key property of the <v-data-table> like so:

<v-data-table
  show-expand
  :headers="headers"
  :items="filteredItems"
  :search="search"
  item-key="sow"
  @click:row="rowClick"
  :hide-default-footer="disablePagination"
  dense
  :disable-pagination="disablePagination"
>

(every record using this component will have the unique sow key), but that didn't work.

The only think I could think if is how I'm editing the data in my mutation:

export const state = () => ({
    active: [],
    closed: [],
    opportunities: [],
})

export const getters = {
  activeProjects: state => state.active,
  closedProjects: state => state.closed,
  opportunities: state => state.opportunities,
}

export const actions = {
  saveProject({ commit }, {projectType, projectData}) {
    commit(types.SAVE_PROJECT, {projectType, projectData});
  }
}

export const mutations = {
  [types.SAVE_PROJECT](state, {projectType, projectData}) {
    // Get project from state list by sow field.
    const index = state[projectType].findIndex(p => p.sow === projectData.sow);
    state[projectType][index] = projectData;
  }
}

as compared to replacing the entire state[projectType] value.

What do I need to do to get the data table to display my updated value?


Solution

  • From the Vue documentation,

    Vue cannot detect the following changes to an array

    • When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
    • When you modify the length of the array, e.g. vm.items.length = newLength

    Replace what you have with

    import Vue from 'vue'; // this should be at the top level
    
    export const mutations = {
      [types.SAVE_PROJECT](state, {projectType, projectData}) {
        // Get project from state list by sow field.
        const index = state[projectType].findIndex(p => p.sow === projectData.sow);
        Vue.set(state[projectType], index, projectData)
      }
    }

    After this, the changes to the array will be detected and the getter will work as expected.