Home > database >  How to use vue to route to different pages and display their title and text?
How to use vue to route to different pages and display their title and text?

Time:12-03

I am making a post website in vue. This is my problem:

There are 2 pages, one for all the blogs, and the other one where you can view the blog. I am working with vuex. When I click on one of the blogs, I get sent to the viewblog page and I see the title of the correct blog, so no problem with that.

What I want to do, seems simple. I want a viewblog page with two items: a title and text. These two should come from the store (vuex).

This is the viewblog page for now:

<template>
  <div >
      <h1>{{ $route.params.title }}</h1>
      // here I want my text from the store.
  </div>
</template>

<script>
export default {
}
</script>

<style>

</style>

And this is the library:



<template>
  <div >
    <Card :post="post" v-for="(post, index) in cards" :key="index" />
  </div>
</template>

<script>
import Card from "../components/Card.vue";

export default {
  components: {
    Card,
  },
  computed: {
    cards() {
      return this.$store.state.cards;
    },
  },
};
</script>

<style>
// ...
</style>

To the library, there belongs a card:

<template>
  <div >
    <router-link
      :post="post"
      :to="{ name: 'Post', params: { id: post.index, title: post.title } }"
    >
      <div ></div>
      <div >{{ post.title }}</div>
    </router-link>
  </div>
</template>

<script>
export default {
  name: "Card",
  props: ["post"],
};
</script>
<style>
// ...
</style>

This is my vue-router:

import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
  },
  {
    path: '/post/:id',
    name: 'Post',
    component: () => import('../views/Post.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

router.beforeEach((to, from, next) => {
  console.log(to)
  let documentTitle = `${ process.env.VUE_APP_TITLE } - ${to.name}`
  if (to.params.title) {
    documentTitle  = ` - ${ to.params.title }`
  }
  document.title = documentTitle
  next()
})

export default router

Finally, the store (vuex):

import { createStore } from "vuex";

export default createStore({
    state: {
        cards: [{
                title: "Blog 1",
                text: "This is blog 1",
                index: 1,
            },
            {
                title: "Blog 2",
                text: "This is blog 2",
                index: 2,
            },
            {
                title: "Blog 3",
                text: "This is blog 3",
                index: 3,
            },
            {
                title: "Blog 4",
                text: "This is blog 4",
                index: 4,
            },
            {
                title: "Blog 5",
                text: "This is blog 5",
                index: 5,
            },
            {
                title: "Blog 6",
                text: "This is blog 6",
                index: 6,
            },
            {
                title: "Blog 7",
                text: "This is blog 7",
                index: 7,
            },
        ],
    },
    mutations: {},
    actions: {},
    modules: {},
});

This is the result in the library: Result (library) And this is the result in the viewblog page when I click on blog 1: Result (view blog) For Clarity: beneath the title, there would be text, which is coming from the store. So in this example that text would be This is blog 1

Thanks for helping me!

CodePudding user response:

Approach 1: pass by route params

You can define a getter function to select the given Blogtext. Pass the :id of the blog-route as a param, so that you can use it as a filter in your getter. Then within that getter define a simple filter logic. I did it with forEach, but there are other options to filter the array, pick one that fits your needs.

Within the component you can map the getter, and call it in a computed property with the id passed via the route, and voila, it should return the text of that specific blog post.

VueX Store

import { createStore } from 'vuex';

export default createStore({
    state: {
        cards: [{
                title: "Blog 1",
                text: "This is blog 1",
                index: 1,
            },
            {
                title: "Blog 2",
                text: "This is blog 2",
                index: 2,
            },
            {
                title: "Blog 3",
                text: "This is blog 3",
                index: 3,
            },
            {
                title: "Blog 4",
                text: "This is blog 4",
                index: 4,
            },
            {
                title: "Blog 5",
                text: "This is blog 5",
                index: 5,
            },
            {
                title: "Blog 6",
                text: "This is blog 6",
                index: 6,
            },
            {
                title: "Blog 7",
                text: "This is blog 7",
                index: 7,
            },
        ],
    },
    getters: {
      blogTextById: (state) => (index) => {
         let foundText;
         state.cards.forEach((card) => { 
           // cast both indexes to Number, so that we won't run into 
           //unexpected type-mismatch. Since index might be coming 
           // from an URL, it's likely that it is stored as a splitted 
           // string, holding only a number. 
           // e.g.: index = "2" instead of index = 2 
           if (Number(card.index) === Number(index)) foundText = card.text;
         });
         return foundText;
      },
    },
});

ViewBlogComponent

<template>
  <div >
      <h1>{{ $route.params.title }}</h1>
      <p>{{ blogText }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters({
      blogTextGetter: 'blogTextById',
    }),
    blogText() {
      return this.blogTextGetter(this.$route.params.id);
    },
  },
}
</script>

Approach 2: pass id as prop, get whole card

This approach needs a few more changes, some of which alter your general approach of using the params of a route. We would introduce a new prop to your viewBlogPage which only represents the id || index of the blog to view. Then we use that ID to fetch the whole card-item and we display all data, that we want to display.

In your LibraryCardComponent you overload the route call with the title param, which is not inherently bad practice, but it's hard to follow, without knowing about this specific behaviour. It also introduces a tight coupling between the component and the $route Object. But since you use it in your beforeEach hook I will let it stay there.

So what do we do?

We change the route definition for Post to enable Passing Props to Route Components

Route Definitions

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
  },
  {
    path: '/post/:id',
    name: 'Post',
    // Here we enable the passing of props.
    // The boolean mode passes all params defined as props with the same name as the param
    // So :id will be available as prop 'id' in the Component.
    props: true,
    component: () => import('../views/Post.vue')
  }
]

Now we have to introduce the prop to our component for later use.

ViewBlogPage

<template>
  <div >
      <h1>{{ blogPost.title }}</h1>
      <p>{{ blogPost.text }}</p>
  </div>
</template>
   
<script>
import { mapGetters } from 'vuex';

export default {
  props: {
    id: {
      required: true,
    }
  },
  computed: {
    ...mapGetters({
      blogById: 'blogById',
    }),
    blogPost() {
      return this.blogById(this.id);
    },
  },
}
</script>

And the getter from approach one could be written as a getBlog Getter instead of a getBlogText Getter

VueX Getters

import { createStore } from 'vuex';

export default createStore({
    state: {
        cards: [{
                title: "Blog 1",
                text: "This is blog 1",
                index: 1,
            },
            {
                title: "Blog 2",
                text: "This is blog 2",
                index: 2,
            },
            {
                title: "Blog 3",
                text: "This is blog 3",
                index: 3,
            },
            {
                title: "Blog 4",
                text: "This is blog 4",
                index: 4,
            },
            {
                title: "Blog 5",
                text: "This is blog 5",
                index: 5,
            },
            {
                title: "Blog 6",
                text: "This is blog 6",
                index: 6,
            },
            {
                title: "Blog 7",
                text: "This is blog 7",
                index: 7,
            },
        ],
    },
    getters: {
      blogById: (state) => (index) => {
         // cast both to Number() to prevent unexpected type-mismatch
         return state.cards.find(c => Number(c.index) === Number(index));
      },
    },
});

With this approach your title and text display is no longer tightly coupled to the route params, but reactively coupled with your state from vueX.

I hope one of the approaches fits your needs.

  • Related