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:
And this is the result in the viewblog page when I click on blog 1:
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.