Search code examples
vue.jsvuex

Not understanding Vuex


I have 2 resources, Projects and Pieces, such that Projects have many Pieces and routing to a specific Piece looks like:

.../projects/:project_id/pieces/:piece_id

in the store, Vuex saves currentProject via an axios call based on this.$route.params.project_id.

When I navigate from .../projects/:project_id/ to .../projects/:project_id/pieces/:piece_id, currentProject is defined as expected.

However, if I refresh the page, currentProject --> undefined.

here is my vue file for the PieceShow view:

<template>
  <div class="ui container">
    <div class="row">
      <div class="col-md-12">
        <div class="row">
          <h3 class="mb-2"> 
            <router-link :to="{name: 'ProjectShow', params: {project_id: currentProject.id}}">{{currentProject.name}}</router-link> 
            - {{ piece.title }}
            <router-link 
              :to="{ name: 'PieceEdit' }" 
              class="ml-5" 
              data-toggle="tooltip" 
              title="Edit this piece"
              v-show="isPieceOwner"
            >
              <i class="fas fa-edit"></i>
            </router-link>
          </h3>
        </div>
        <div class="fixed-height-600 bg-white p-3" v-html="`${piece.body}`"></div>
      </div>
    </div>
  </div>
</template>

<script>
import PiecesService from '@/services/PiecesService'
export default {
  name: 'PieceShow',
  components: {
  },
  data () {
    return {
      piece: {}
    }
  },
  computed: {
    // get current user and check to see if they belong to this piece
    isPieceOwner() {
      return this.piece.users_id === this.$store.state.user.id
    },
    currentUser() {
      return this.$store.state.user
    },
    currentProject() {
      return this.$store.state.currentProject
    }
  },
  async mounted () {
    await this.getPieceById ();
    // if (process.env.NODE_ENV === 'production') {
    //   window.analytics.page('Piece Page Visited', {
    //     user: this.$store.getters.user.username,
    //     piece: this.piece.title
    //   }) // from segment.io docs
    //   window.analytics.track('Piece Viewed', {
    //     title: this.piece.title,
    //     user: this.currentUser.username
    //   });
    // }
  },
  methods: {
    async getPieceById () {
      const response = await PiecesService.getPieceById({
        id: this.$route.params.piece_id
      })
      console.log('In PieceShow, this piece (retrieved from Express) is: ', response.data );
      this.piece = response.data;
    },
    async convert2Docx () {
      const response = await PiecesService.convert2Docx({
        id: this.$route.params.piece_id
      })
      console.log('this is the response object: ', response)
    }
  }
}
</script>

I set currentProject in the ProjectShow component when we retrieve it for the view like this:

this.$store.commit("setCurrentProject", this.project);

Assuming I don't unset it, why can I not then call this.$store.state.currentProject without having to set it again when I get to the PieceShow view?


Solution

  • The Vuex store is reset to its initial state when you refresh the page, similar to how a normal page is reset to its initial state when you refresh it. To persist (part of) the Vuex store, you need to use a plugin for that. You could use, for example, vuex-persistedstate.

    While this might help if you refresh, it would obviously not do anything if you would for example click on a link to a piece in a different project.

    The other thing that when you refresh, and you have a route in the url bar that points to the route of PieceShow, Vue router will not visit ProjectShow at all.


    You should always build your views with the knowledge that someone might not have visited any other route. That said, there are several ways you can get around simply duplicating your code to retrieve a project.

    You can move the code for retrieving your project to a Vuex action. If the project is already loaded, just return early, otherwise retrieve the project. Guard against non-existence by using a v-if on your component so it does not render before the project has been loaded. You can even show a loader based on a variable you put in the vuex store in that case.

    An other way is to nest your routes in a way that you have a route for /projects/:projectId and one for the subroute pieces/:pieceId. In the view for /projects/:projectId you can then do something like below. In your router.js you can then define an attribute children with a subroute that will be mounted in the router-view for /projects/:projectId.

    <template>
      <router-view v-if="currentProject" />
    </template>
    
    <script>
    export default {
      computed: {
        currentProject() {
          return this.$store.state.currentProject
        }
      },
    
      created () {
        const response = await PiecesService.getPieceById({
          id: this.$route.params.piece_id
        })
        const project = response.data
    
        this.$store.commit("setCurrentProject", project);
      }
    }
    </script>