Vue 2, Vuex & Vue-router

Decoupled components with centralized states problem

HTML

<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>

SCSS

* {
  box-sizing: border-box;
}

.listing {
  list-style-type: none;
  overflow: hidden;
  padding: 0;
  li {
    float: left;
    width: 175px;
    text-align: center;
    border: 1px #ddd solid;
    background: white;
    margin: 5px;
    cursor: pointer;
    img {
      width: 100%;
      margin-bottom: 4px;
    }
    > 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;
  }
}

Babel + JSX

const db = [
  { id: '2', name: 'Item #1', image: 'http://lorempicsum.com/simpsons/350/200/1', votes: 0 }, 
  { id: '3', name: 'Item #2', image: 'http://lorempicsum.com/simpsons/350/200/2', votes: 0 }, 
  { id: '4', 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)
      this.$emit('upvote', this.item.id)
    }
  },
  props: ['item']
}

const ListingView = {
	name: 'ListingView',
	template: `
  	
    <ul class="listing">
    	<button v-on:click="update()">Update</button>
    	<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 @upvote="upvote"></votes> 
    	</li>
		</ul>
  `,
  created () {
	  this.$store.dispatch('fetch')
  },
  methods: {
  	upvote(id) { this.$store.dispatch('upvote',id) },
    update() { this.$store.commit('UPDATE', 1) },
  },
  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 @upvote="upvote"></votes> </h1>
    		<img :src="item.image" />
	    </div>
		</div>
  </div>`,
  computed: {
  	item: function () {
    	return this.$store.state.items.find(item => item.id === this.$route.params.id)
    }
  },
  created () {
	  this.$store.dispatch('open', this.$route.params.id) // I need this because user can navigate via Copy/Paste URL
  },
  methods: {
  	upvote(id) { this.$store.dispatch('upvote',id) }
  },
  components: { Votes }
}

const store = new Vuex.Store({
	state: {
  	items: [],
    opened: {}
  },
 ...