Creating separate Vuex stores for dynamic components

This question really had me stumped for a while. I couldn't find the answer here (asking around didn't help either). However, after conducting some research and seeking advice from various sources, I believe I have finally arrived at a solution to this problem.

If you have a question that you already know the answer to, and you would like to share that knowledge publicly so that others (including yourself) can benefit from it later on.

Admittedly, my solution may not be perfect - in fact, I am well aware that it is not. That's precisely why I'm sharing it here - to receive feedback and improve upon it.

It's important to note that I did not utilize any actions in this example. The concept remains the same.

Let's start by defining the issue at hand:

Imagine we have a file called App.vue which dynamically generates its own local component named Hello.

<template>
  <div id="app">
    <div>
      <hello v-for="i in jobs" :key="i" :id="i"></hello>
      <button @click="addJob">New</button>
    </div>
  </div>
</template>   

<script>
import Hello from './components/Hello'

export default {
  components: {
    Hello
  }...

store.js

export const store = new Vuex.Store({
  state: {
    jobs: []
  }
})

We are using the v-for directive to generate components by looping through an array called jobs. Our current store only consists of a single state with an empty array. The New button should perform two tasks:

1) create a new component Hello, essentially adding an element to the jobs array (let them be numbers), which will serve as the key and id for the <hello> component, and then passed as props to the local component.

2) generate individual stores - known as modules - to contain data specific to each newly created component.

Hello.vue

<template>
  <div>
    <input type="number" :value="count">
    <button @click="updateCountPlus">+1</button>
  </div>
</template>

export default {
  props: ['id']
}

A straightforward component featuring an input field and a button that increments the count by 1.

Our objective is to achieve something along these lines: https://i.sstatic.net/6MA1X.jpg

Answer №1

When initializing the NEW button for the first task - creating components - a mutation is added to our store.js

 mutations: {
    addJob (state) {
      state.jobs.push(state.jobs.length + 1)
...
}

Next, developing local modules. In this step, we utilize reusableModule to produce multiple instances of a module. This module is stored in a separate file for convenience. Additionally, a function is used to define the module's state.

const state = () => {
  return {
    count: 0
  }
}

const getters = {
  count: (state) => state.count
}

const mutations = {
  updateCountPlus (state) {
    state.count++
  }
}

export default {
  state,
  getters,
  mutations
}

In order to implement reusableModule, it must be imported and dynamically registered as a module.

store.js

import module from './reusableModule'

const {state: stateModule, getters, mutations} = module

export const store = new Vuex.Store({
  state: {
    jobs: []
  },
  mutations: {
    addJob (state) {
      state.jobs.push(state.jobs.length + 1)
      store.registerModule(`module${state.jobs.length}`, {
        state: stateModule,
        getters,
        mutations,
        namespaced: true // making our module reusable
      })
    }
  }
})

Following that, we establish a connection between Hello.vue and its storage. Accessing the storage requires defining our own getters and mutations.

Home.vue

<script>

export default {
  props: ['id'],
  computed: {
     count () {
        return this.$store.getters[`module${this.id}/count`]
     }
  },
  methods: {
    updateCountPlus () {
        this.$store.commit(`module${this.id}/updateCountPlus`)
     } 
  }
}
</script>

While managing numerous getters, mutations, and actions, consider utilizing {mapGetters} or {mapMutations} for efficiency when working with multiple modules.

The code associated with these helpers executes when the component's module is initialized during app launch, not when the component is created. Hence, the utilization of these helpers is limited to cases where the module name is known beforehand.

A practical approach involves separating getters and mutations into objects to maintain clarity and organization within the code structure.

<script>
import computed from '../store/moduleGetters'
import methods from '../store/moduleMutations'

export default {
  props: ['id'],
  computed,
  methods
}
</script>

Returning to the App component, remember to execute the necessary mutation along with creating relevant getters to access data residing in the modules.

store.js

export const store = new Vuex.Store({
  state: {
    jobs: []
  },
  getters: {
    jobs: state => state.jobs,
    sumAll (state, getters) {
      let total = 0
      for (let i = 1; i <= state.jobs.length; i++) {
        total += getters[`module${i}/count`]
      }
      return total
    }
  } 
...

To complete the code in the App component:

<script>
import Hello from './components/Hello'
import {mapMutations, mapGetters} from 'vuex'

    export default {
      components: {
        Hello
      },
      computed: {
        ...mapGetters([
          'jobs',
          'sumAll'
        ])
      },
      methods: {
        ...mapMutations([
          'addJob'
        ])
      }
    }
    </script>

Answer №2

Greetings and thank you for sharing your question along with the solution.

Recently, I delved into learning Vuex and encountered a similar issue. After reviewing your solution, I devised mine without the necessity of registering additional modules. I personally view adding new modules as excessive and frankly fail to grasp the rationale behind it. However, there's a chance that I may have misconstrued the problem at hand.

In order to enhance clarity and illustrative purposes, I replicated your markup with a few modifications:

The components included are:

  1. JobList.vue - primary custom component
  2. Job.vue - child custom component within job-list
  3. jobs.js - vuex store module file

Description of JobList.vue (responsible for enclosing the job(s) list items):

<template>
    <div>
        <job v-for="(job, index) in jobs" :data="job" :key="job.id"></job>

        <h3>Create New Job</h3>
        <form @submit.prevent="addJob">
            <input type="text" v-model="newJobName" required>
            <button type="submit">Add Job</button>
        </form>
    </div>
</template>

<script>
    import store from '../store/index'
    import job from './job';

    export default {
        components: { job },
        data() {
            return {
                newJobName: ''
            };
        },
        computed: {
            jobs() {
                return store.state.jobs.jobs;
            }
        },
        methods: {
            addJob() {
                store.dispatch('newJob', this.newJobName);
            }
        }
    }
</script>

Details of Job.vue:

<template>
    <div>
        <h5>Id: {{ data.id }}</h5>
        <h4>{{ data.name }}</h4>
        <p>{{ data.active}}</p>
        <button type="button" @click="toggleJobState">Toggle</button>
        <hr>
    </div>
</template>

<script>

    import store from '../store/index'

    export default {
        props: ['data'],
        methods: {
            toggleJobState() {
                store.dispatch('toggleJobState', this.data.id);
            }
        }
    }

</script>

Lastly, here is the jobs.js file defining the Vuex module:

export default {
    state: {
        jobs: [
            {
                id: 1,
                name: 'light',
                active: false
            },
            {
                id: 2,
                name: 'medium',
                active: false
            },
            {
                id: 3,
                name: 'heavy',
                active: false
            }
        ]
    },

    actions: { //methods
        newJob(context, jobName) {
            context.state.jobs.push({
                id: context.getters.newJobId,
                name: jobName,
                active: false
            });
        },
        toggleJobState(context, id) {
            context.state.jobs.forEach((job) => {
                if(job.id === id) { job.active = !job.active; }
            })
        }
    },

    getters: { //computed properties
        newJobId(state) { return state.jobs.length + 1; }
    }
}

This setup allows for adding new jobs to the store and individually controlling each job using the "active" property, eliminating the requirement for an additional custom Vuex module.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A straightforward method of transmitting data from JavaScript to the Python back-end within a Streamlit application

Utilizing the st.components.v1.iframe, I integrated an authentication system that sends a token back to the parent when a user is authenticated. The iframe content is as follows: <script src="remote/auth.js"></script> <scri ...

Using javascript within a PHP file

I'm struggling to implement this JavaScript function within a PHP page (footer.php), but it's not functioning as expected. I've experimented with various approaches, even attempting to include the script within a PHP echo statement, but that ...

ES6 Generators: lack of informative stack trace when using iterator.throw(err)

The ES6 approach: iterator.throw(err) is often explained as inserting an exception as if it happened at the yield statement within the generator. The challenge lies in the fact that the stack trace of this exception does not include any information about t ...

AngularJS - issue with directive functionality on dynamically inserted classes

Check out this plnkr - I've got a button that toggles the class toggled on the body element. I've also developed a directive to trigger an alert when the toggled class is applied to the body element, but for some reason it's not working. H ...

How can I show a legend entry for every column in Amcharts?

Take a look at this code snippet: http://jsfiddle.net/ouLed1fp/ How can I create a legend entry for each column in the chart? Also, is there a way to display the column names next to them, providing a clear legend? <div id="chartdiv" style="width: 10 ...

Access a PHP file using XMLHttpRequest to run additional JavaScript code

My main page, referred to as Main.php, contains a button that triggers the display of results from Results.php within a div (divResults) on Main.php. The HTML content "These Are The Results" returned by Results.php is successfully displayed in the divResu ...

Issue with retrieving data from an external provider

I'm attempting to fetch data so I can tokenize my Credit Card, but I am required to use this address: this.state.data.CardRegistrationURL == "https://homologation-webpayment.payline.com/webpayment/getToken" Here is my fetch method: postI ...

One common issue is being unable to target input[type=file] when multiple forms are present on a page using JavaScript

Question: I have multiple dynamic forms on a web page, each with a file input field. How can I specifically target the correct file input using $(this) in JavaScript? Here is an example of one of my forms: <form enctype="multipart/form-data" action="c ...

The total number of items in the cart is experiencing an issue with updating

For a recording of the issue, click here: While everything works fine locally, once deployed to production (vercel), it stops working. I've tried numerous approaches such as creating a separate state in the cart, using useEffect with totalQuantity in ...

The playwright brings the curtain down on a blank page without a single word

I am working with code snippets const {chromium} = require('playwright'); (async () => { const userDataDir = '\NewData'; const browser = await chromium.launchPersistentContext(userDataDir,{headless:false}); const pag ...

What is the best way to ensure that empty strings are not included in the length of my array?

I have encountered an issue with a JSON file that I fetch. The array syllables is capable of holding up to strings max. However, when I exclude 2 words, I end up with 2 words and 2 empty strings. Nevertheless, my front-end still expects 4 "strings". So, it ...

Error message: When accessing react-native camera roll, the message "this.state.photos is not a valid object" appears

Encountering an error while attempting to implement camera roll functionality in my demo app. The error states, "null is not an object (evaluating 'this.state.photos')" View iOS Error Message. I am a beginner developer working on my first react-n ...

The Express server is failing to deliver a response to the client when using the fetch method

Currently, I am utilizing express for the server side of my project. To send a post request from the client to the server, I am using fetch. The data that I am sending to the server is being successfully transmitted and displayed. However, I am encounteri ...

What is the method for iterating through rows using csv-parser in a nodejs environment?

I'm working on a script to automate the generation of code for my job. Sometimes I receive a .csv file that contains instructions to create table fields, sometimes even up to 50 at once! (Using AL, a Business Central programming language.) Here is th ...

Unlocking the Power of Ajax for Displaying Wordpress Queries

Running into some issues trying to use jQuery for displaying content. I have set up three buttons that, when clicked, load data from a php file on my server. Everything functions properly except when attempting to retrieve Wordpress posts. An error message ...

Issue TS7053 occurs when trying to access any index of the target of a React.FormEvent<HTMLFormElement>

I've been working on adapting this tutorial to React and TypeScript. Here is the code snippet I have implemented for handling the onSubmit event: const handleSignUp = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); ...

I am having trouble grasping certain syntax in JavaScript when it comes to using `${method_name}`

I'm having trouble understanding some of the syntax in this code, particularly ${method_name}. I'm not sure what we are achieving by passing the method name within curly braces. global._jsname.prototype.createEELayer = function (ftRule) { if ...

Circular movement in ThreeJS within a specified duration

I am currently working on creating a circular motion for an object within a set time frame. This project involves designing a clock with smooth motion. Instead of simply updating the position to fixed coordinates every time .getSeconds() changes, I aim t ...

What is causing the recurring failure of file input changes to take effect?

Here is the code snippet I'm working with: <input type="file" #fileInput ng2FileSelect [uploader]="uploader" (onFileSelected)="onFileSelected($event)" /> Along with the handler function: public onFileSelected(e: Fi ...

utilize ajax success method to extract json data

I'm currently working on displaying and parsing JSON data in the success function of an AJAX call. This is what I have so far: AJAX: data = "Title=" + $("#Title").val() + "&geography=" + $("#geography").val(); alert(data); url= "/portal/getRe ...