4

I try to find a way to use vuex with reusable component which store data in a store. The thing is, I need the store to be unique for each component instance.

I thought Reusable module of the doc was the key but finally it doesn't seem to be for this purpose, or i didn't understand how to use it.

The parent component: (the prop “req-path” is used to pass different URL to make each FileExplorer component commit the action of fetching data from an API, with that url path)

<template>
  <div class="container">
    <FileExplorer req-path="/folder/subfolder"></FileExplorer>
    <FileExplorer req-path="/anotherfolder"></FileExplorer>
  </div>
</template>

<script>
import { mapState, mapGetters } from "vuex";
import FileExplorer from "@/components/FileExplorer.vue";

export default {
  components: {
    FileExplorer
  }
};
</script>

The reusable component:

<template>
 <div class="container">
      <ul v-for="(item, index) in folderIndex" :key="index">
        <li>Results: {{ item.name }}</li>
      </ul>
    </div>
 </div>
</template>

<script>
import { mapState, mapGetters } from "vuex";

export default {
  props: ["reqPath"],
  },
  computed: {
    ...mapState("fileExplorer", ["folderIndex"])
  },
  created() {
    // FETCH DATA FROM API
    this.$store
      .dispatch("fileExplorer/indexingData", {
        reqPath: this.reqPath
      })
      .catch(error => {
        console.log("An error occurred:", error);
        this.errors = error.response.data.data;
      });
  }
};
</script>

store.js where I invoke my store module that I separate in different files, here only fileExplorer module interest us. EDIT : I simplified the file for clarity purpose but I have some other state and many mutations inside.

import Vue from 'vue'
import Vuex from 'vuex'

// Import modules
import { fileExplorer } from '@/store/modules/fileExplorer'

Vue.use(Vuex)

export default new Vuex.Store({
modules: {
    fileExplorer,
…
  }
})

@/store/modules/fileExplorer.js

import ApiService from "@/utils/ApiService"

export const fileExplorer = ({
namespaced: true,

  state: {
    folderIndex: {},
},

  mutations: {
    // Called from action (indexingData) to fetch folder/fil structure from API
    SET_FOLDERS_INDEX(state, data) {
      state.folderIndex = data.indexingData
},

actions: {
    // Fetch data from API using req-path as url 
    indexingData({
      commit
    }, reqPath) {
      return ApiService.indexingData(reqPath)
        .then((response) => {
          commit('SET_FOLDERS_INDEX', response.data);
        })
        .catch((error) => {
          console.log('There was an error:', error.response);
        });
    }
  }
});

I need each component to show different data from those 2 different URL, instead i get the same data in the 2 component instance (not surprising though).

Thanks a lot for any of those who read all that !

NuoNuo LeRobot
  • 370
  • 3
  • 16
  • Sounds like `folderIndex` should not be stored in global state and should instead be stored in local state for each `FileExplorer` component. Return `ApiService.indexingData(reqPath)` and handle the response in the `created` method where you can add it to local state – Chase DeAnda Nov 05 '19 at 22:47
  • Sorry I forgot to mention that I simplified my store module file but I have some other state and many mutations inside. If it was just to fetch data, I would have use your solution indeed. – NuoNuo LeRobot Nov 05 '19 at 23:09

1 Answers1

3

Module reuse is about when you are creating multiple modules from the same module config.


First, use a function for declaring module state instead of a plain object.

If we use a plain object to declare the state of the module, then that state object will be shared by reference and cause cross store/module state pollution when it's mutated.

const fileExplorer = {
  state () {
    return {
      folderIndex: {}
    }
  },
  // mutations, actions, getters...
}

Then, dynamically register a new module each time a new FileExplorer component is created and unregister that module before the component is destroyed.

<template>
 <div class="container">
      <ul v-for="(item, index) in folderIndex" :key="index">
        <li>Results: {{ item.name }}</li>
      </ul>
    </div>
 </div>
</template>

<script>
import { fileExplorer } from "@/store/modules/fileExplorer";
import store from "@/store/index";

var uid = 1

export default {
  props: ["reqPath"],
  data() {
    return {
      namespace: `fileExplorer${uid++}`
    }
  },
  computed: {
    folderIndex() {
      return this.$store.state[this.namespace].folderIndex
    }
  },
  created() {
    // Register the new module dynamically
    store.registerModule(this.namespace, fileExplorer);

    // FETCH DATA FROM API
    this.$store
      .dispatch(`${this.namespace}/indexingData`, {
        reqPath: this.reqPath
      })
      .catch(error => {
        console.log("An error occurred:", error);
        this.errors = error.response.data.data;
      });
  },
  beforeDestroy() {
    // Unregister the dynamically created module
    store.unregisterModule(this.namespace);
  }
};
</script>

You no longer need the static module registration declared at store creation.

export default new Vuex.Store({
  modules: {
    // fileExplorer, <-- Remove this static module
  }
})
Ricky Ruiz
  • 25,455
  • 6
  • 44
  • 53
  • Hello Ricky, Thanks a lot for your detailed answer, seems promising. I tried to write everything exactly as you did but I got this error: FileExplorer.vue?51d3:71 Uncaught TypeError: Cannot read property 'modulePath' of undefined at eval (FileExplorer.vue?51d3:71) When i click on the eval it shows the line making problem is this one: computed: { ...mapState(this.modulePath, ["folderIndex"]) }, – NuoNuo LeRobot Nov 06 '19 at 19:13
  • @NuoNuoLeRobot That makes sense, we don't have access to `this` Vue instance when we are calling `mapState`, I edited the answer to make it work. I'll see if I can come up with a solution to be able to use`mapState`. – Ricky Ruiz Nov 06 '19 at 19:51
  • Now I got this error: Vue warn]: Error in beforeCreate hook: "Error: [vuex] module path must be a string or an Array." Not sure it's happy with the dynamic uid thing – NuoNuo LeRobot Nov 06 '19 at 20:07
  • If i console.log(this.modulePath, uid, fileExplorer); in the beforeCreeated hook i get undefined 1 undefinded [...] many errors [...] undefined 2 undefinded. In my store module i declared const fileExplorer : {} but i never export it, isn't it a mistake ? if yes, how shall i export it ? – NuoNuo LeRobot Nov 06 '19 at 20:16
  • The `beforeCreated` lifecycle hook is called before data observation, that's why they are undefined, we'll need to change some things. – Ricky Ruiz Nov 06 '19 at 20:23
  • omg i feel so sorry you are spending so much time on this... thank you so much – NuoNuo LeRobot Nov 06 '19 at 20:28
  • @NuoNuoLeRobot It's okay, don't worry I'm happy to help. You will need to move the module registration to the `created` lifecycle hook to be able to access data. I edited the answer. – Ricky Ruiz Nov 06 '19 at 21:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201975/discussion-between-nuonuo-lerobot-and-ricky). – NuoNuo LeRobot Nov 06 '19 at 22:07
  • Great tip! I wanted to `provide` that `namespace` to children but provide needs to be established `beforeCreate` while data is not available till `created` so I decided to use a prop `namespace` with default value instead of `data` for this. Seems to work but feels strange :) Any advice? – Kuba Jan 18 '21 at 10:09