4

The problem with share states is that is difficult to reuse actions and mutations in differents components.

Lets imagine that we we have a component Votes. This component allow users to vote on item

const Votes = {
  template: `<span>
        <i>{{ item.votes }}</i> <a href="#" @click.prevent="upvote">+</a>
    </span>
    `,
   methods: {
     upvote: function() {
       this.$store.dispatch('upvote', this.item.id)
     }
   },
   props: ['item']
}

So when user click on +, a action upvote is dispatch.

But how to reuse this component in two views, a list that list all items, and a details that display details about the item.

In both cases, we allow users to vote on item.

Details view List view

[ADDED] Vue-router

Users can navigate via URL, E.g. /item/a

In this case, should use router params to find item in database.

The store.items are empty!


The problem begins on store..

state: { items: [], opened: {} },
  actions: {
    open: function({commit, state}, payload) {
        let it = db.find(item => payload === item.id) // Find in db because user can navigate via Copy/Paste URL
      commit('SET_OPENED', it)
    },
    upvote: function({commit, state}, payload) {
        let it = state.items.find(item => payload === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView (our details view) should use state.opened
      commit('SET_VOTE', { id: it.id, votes: it.votes + 1 })
    }
  },
  mutations: {
    SET_VOTE: function(state, payload) {
            let it = state.items.find(item => payload.id === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView (our details view) should use state.opened
      console.log('Voted', db, it)
      Vue.set(it, 'votes', payload.votes)
    },
    SET_OPENED: function(state, payload) {
        Vue.set(state, 'opened', payload)
    }
  }

upvote and SET_VOTE are action and mutations that are called from diferents points (differents views), so the state is diferente.

Question

How to reuse same actions/mutations in differents views with differents states?

[Added] Remember

  1. User can navigate via URL, e.g /item/a and should display item
  2. The objetive is reuse actions/mutations and components. So duplicate all will no resolve this issue.

Full source....

const db = [{
  id: 'a',
  name: 'Item #1',
  image: 'http://lorempicsum.com/simpsons/350/200/1',
  votes: 0
}, {
  id: 'b',
  name: 'Item #2',
  image: 'http://lorempicsum.com/simpsons/350/200/2',
  votes: 0
}, {
  id: 'c',
  name: 'Item #3',
  image: 'http://lorempicsum.com/simpsons/350/200/3',
  votes: 0
}]

const Votes = {
  name: 'Votes',
  template: `<span>
	  	<i>{{ item.votes }}</i> <a href="#" @click.prevent="upvote">+</a>
    </span>
	`,
  methods: {
    upvote: function() {
      this.$store.dispatch('upvote', this.item.id)
    }
  },
  props: ['item']
}

const ListingView = {
  name: 'ListingView',
  template: `
    <ul class="listing">
    	<li v-for="item in $store.state.items">
				<router-link :to="{ name: 'item', params: { id: item.id }}">
      		<img :src="item.image" />
	  	    <br>{{ item.name }}	      
	      </router-link>
      	Votes: <votes :item=item></votes> 
    	</li>
		</ul>
  `,
  created() {
    this.$store.dispatch('fetch')
  },
  components: {
    Votes
  }
}

const ItemView = {
  name: 'ItemView',
  template: `<div class="item-view">
  		<router-link class="back-listing" :to="{name: 'listing'}">Back to listing</router-link>
	  	<div class="item">
  	  	<h1>{{ item.name }} <votes :item=item></votes> </h1>
    		<img :src="item.image" />
	    </div>
		</div>
  </div>`,
  computed: {
    item: function() {
      return this.$store.state.opened
    }
  },
  created() {
    this.$store.dispatch('open', this.$route.params.id) // I need this because user can navigate via Copy/Paste URL
  },
  components: {
    Votes
  }
}

const store = new Vuex.Store({
  state: {
    items: [],
    opened: {}
  },
  actions: {
    fetch: function({
      commit, state
    }, payload) {
      commit('SET_LIST', db.map(a => Object.assign({}, a))) // Just clone the array
    },
    open: function({
      commit, state
    }, payload) {
      let it = db.find(item => payload === item.id) // Find in db because user can navigate via Copy/Paste URL
      commit('SET_OPENED', it)
    },
    upvote: function({
      commit, state
    }, payload) {
      let it = state.items.find(item => payload === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView should use state.opened
      commit('SET_VOTE', {
        id: it.id,
        votes: it.votes + 1
      })
    }
  },
  mutations: {
    SET_VOTE: function(state, payload) {
      let it = state.items.find(item => payload.id === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView should use state.opened
      console.log('Voted', db, it)
      Vue.set(it, 'votes', payload.votes)
    },
    SET_OPENED: function(state, payload) {
      Vue.set(state, 'opened', payload)
    },
    SET_LIST: function(state, payload) {
      Vue.set(state, 'items', payload)
    }
  }
})
const router = new VueRouter({
  routes: [{
    name: 'listing',
    path: '/',
    component: ListingView
  }, {
    name: 'item',
    path: '/item/:id',
    component: ItemView
  }]
})
new Vue({
  el: '#app',
  store,
  router
})
* {
  box-sizing: border-box;
}
.listing {
  list-style-type: none;
  overflow: hidden;
  padding: 0;
}
.listing li {
  float: left;
  width: 175px;
  text-align: center;
  border: 1px #ddd solid;
  background: white;
  margin: 5px;
  cursor: pointer;
}
.listing li img {
  width: 100%;
  margin-bottom: 4px;
}
.listing li > a:hover {
  background: #eee;
}
.item-view {
  text-align: center;
}
.item {
  padding: 10px;
}
a {
  font-size: 16px;
  display: inline-block;
  padding: 10px;
  border: 1px #ddd solid;
  background: white;
  color: black;
  margin: 10px;
  &.back-listing {
    position: absolute;
    left: 0;
    top: 0;
  }
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
  <router-view></router-view>
</div>

Or in fiddle: http://jsfiddle.net/Ridermansb/sqmofcbo/3/

Was add another post (cross-posting) in Vue Forum

1 Answer 1

2

Just a a brief glance at your code, your issue is primarily that you are copying your current item into state.opened. Instead of doing that you should store a reference of the id of the currently opened item in state.opened and use that id to modify state.items.

Working example with a few extra comments on fixing it.

http://jsfiddle.net/d30o31r8/

Sign up to request clarification or add additional context in comments.

2 Comments

What is the issue in particular? Your data store resets on every hard refresh, so it will always be zero when the page loads that way.
see edited post [Vue-router] session. When user navigate to /item/a your code will fail because date store is reset.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.