VueJS and dynamic titles - vue.js

Trying to use vue-meta
I can't understand how to set title based on XHR response. So far I have:
<script>
export default {
name: 'Model',
data() {
return {
model: [],
}
},
metaInfo: {
title: 'Default Title',
titleTemplate: '%s - site slogan'
},
methods: {
getModels() {
window.axios.get(`/api/${this.$route.params.manufacturer}/${this.$route.params.model}`).then((response) => {
this.model = response.data;
this.metaInfo.title = response.data.model_name; // THIS NOT WORKING
});
}
},
watch: {
$route(to, from) {
if ( to.name === 'model' ) {
this.getModels();
}
},
},
created() {
this.getModels();
}
}
</script>
when I try to set
this.metaInfo.title = response.data.model_name;
Getting error: Uncaught (in promise) TypeError: Cannot set property 'title' of undefined
So this.metaInfo is undefined...
I need my title be based on response from XHR request.

You need to use the function form of metaInfo and have it get updates from reactive data
<script>
export default {
data() {
return {
title: "Default Title",
// ...
};
},
metaInfo() {
return {
title: this.title,
// ...
};
},
methods: {
getModels() {
window.axios.get("url...").then((response) => {
this.title = response.data.model_name;
});
}
},
// ...

I assume you call this.metaInfo.title = response.data.model_name; inside a method on the vue instance. The problem I see is that you should put the metaInfo object inside the return object from data(). Like this:
data() {
return {
model: [],
metaInfo: {
title: 'Default Title',
titleTemplate: '%s - site slogan'
},
};
},

Here is my solution:
I have a root component in my SPA app: App.vue with this code in it:
export default {
/**
* Sets page meta info, such as default and page-specific page titles.
*/
metaInfo() {
return {
titleTemplate(titleChunk) {
const suffix = "Marvin Rodank's dank site";
return titleChunk ? `${titleChunk} - ${suffix}` : suffix;
},
};
},
};
That sets up my default page title for all pages, and then after that, the answer by Stephen Thomas contains the key logic.
For all pages with static page titles, it's easy:
metaInfo() {
return { title: 'List examples' };
},
But dynamic page titles were more difficult, but still easy once you realize the page loads in two phases:
phase 1: browser displays the default page title
phase 2: page title is updated with the dynamic title
metaInfo() {
return {
title: this.example.name,
};
},
In the dynamic title example there, my child component fetches the object this.example from an API endpoint, so it is important to note that this.$metaInfo().title updates itself when this.example is populated.
You could test it with code such as this:
metaInfo() {
return {
title: this.example.name,
};
},
mounted() {
const obj = {
name: 'Sally',
age: 1337,
};
this.example = obj;
},

Related

How to render portable block from sanity in vue app

I'm testing around with sanity right now and I am trying to display portable text of sanity on my vue frontend. Sadly it does not work as expected.
So i use the npm package sanity-blocks-vue-component to render the portable text and the normal fetch function which is privided in the docs of sanity.
This is my file where I fetch it successfully but the SanityBlock does nothing:
<template>
<div :class="name">
<SanityBlocks :block="content.impressumContent" />
</div>
</template>
<script>
import { SanityBlocks } from 'sanity-blocks-vue-component';
import sanity from "../../sanity.js";
const query = `*[_type == "impressum"]{
impressumContent,
}
`
export default {
components: {
SanityBlocks
},
data() {
return {
name: 'p-impressum',
loading: true,
content: [],
}
},
created() {
this.fetchData();
},
methods: {
fetchData() {
this.error = this.impressum = null;
this.loading = true;
sanity.fetch(query).then(
(content) => {
this.loading = false;
this.content = content;
},
(error) => {
this.error = error;
}
);
}
}
}
</script>
And that's the scheme that I fetch:
export default {
name: 'impressum',
type: 'document',
title: 'Impressum',
fields: [
{
name: 'impressumContent',
title: 'Impressum Content',
type: 'array',
of: [
{
type: 'block'
},
]
}
]
}
I don't get my head around why this isn't working. Hopefully somone can help me.
Thaanks:))

How can I make the vue-router change when changing parameters? (vuex)

I'm making an app that has advanced search api.
You can choose what to look for and how to sort the results. The problem is that the page (vue-router) is updated only when the request changes, but it also should be updated when you change the search terms
How i can do this? I don't even have any ideas.
There is my code that is responsible for requesting the API and updating the router when the request is updated
export default {
name: "Search",
data: function () {
return {
selectedTag: 'story',
selectedBy: '',
};
},
components: {
'Item': Item
},
mounted() {
this.items = this.getItems(this.id)
},
beforeRouteUpdate(to, from, next) {
this.items = this.getItems(to.params.id);
next();
},
methods: {
getItems(id) {
this.items = this.$store.dispatch('FETCH_SEARCH_RESULTS', {id, tag: this.selectedTag, by: this.selectedBy});
return this.items;
},
},
created: function () {
this.getItems(this.$route.params.id);
},
computed: {
items: {
get() {
return this.$store.state.searchResults;
},
set(value) {
this.$store.commit("APPEND_SEARCH_RESULTS", value);
}
}
}
}

Vue: Not rendering the component for the second time on routing

I am facing behavior of Vue which I don't understand. I am using routing between components.
methods: {
redirectToLogin() {
this.$router.push("/login");
},
redirectToRegister() {
this.$router.push("/register");
}
}
So when load the app, route Login component, login successfully and then log out to component with methods above. After this when I am trying to route to login again the Login component is not rendered, but the route is shown in the address line
Below you can see my routes
routes: [
{path: '/', name: 'Hello', component: Hello},
{path: "/login",name:'Login', component: Login},
{path: "/register",name:'Register', component: Register},
{path: "/user/:id",name:'User', component: User},
{path: "/reset",name:'PasswordReset', component: PasswordReset},
]
I am also using Vuex can it somehow affect such behaviour?
UPD:
When I log out I see the following error in my console
TypeError: "t._data is undefined"
VueJS 14
$destroy
destroy
_
T
x
$
ji
_update
r
get
run
Yn
he
ue
vue.runtime.esm.js:1888:12
UPD 2 : Components
This is first component loaded to the app. After logging out route leads here and none of the router links work
export default {
name: 'Hello',
data() {
return {
msg: 'Work With your projects in agile manner'
}
}
}
Login component
export default {
name: "Login",
data() {
return {
errorOccurred: false,
errorMessage: '',
credentials: {
login: '',
password: ''
},
remember: false
}
},
methods: {
submit() {
this.$store.dispatch('loginUser', this.credentials).then(() => {
this.errorMessage = this.getError;
if (this.errorMessage.length) {
this.errorOccurred = true;
} else {
this.$router.push({path: '/user/' + this.getId});
}
});
this.errorOccurred = false;
},
resetPassword() {
this.$router.push("/reset");
},
},
computed: {
loginValidation() {
return this.credentials.login.length > 0
},
passwordValidation() {
return this.credentials.password.length > 0
},
getError() {
return this.$store.getters.getErrorMsg;
},
getId() {
return this.$store.getters.getUserId;
}
},
}
User component routed from login.
import NavbarCommon from "./NavbarCommon";
export default {
name: "User",
components: {NavbarCommon},
data(){
},
methods: {
loadAvatar(){
let image = '../../assets/emptyAvatar.png';
if ('avatar' in this.getUser){
image = this.getUser.avatar;
}
return image;
}
},
computed:{
getUser() {
return this.$store.getters.getUser;
}
}
}
And two two more components.
NavbarComponent - common navbar for several components
import NavbarRight from "./NavbarRight";
export default {
name: "NavbarCommon",
components: {NavbarRight},
methods:{
routeToUser(){
this.$router.push({path: '/user/' + this.getUser});
},
routeToProject(){
this.$router.push({path: '/project/' + this.getProject});
}
},
computed:{
getUser() {
return this.$store.getters.getUserId;
},
getProject(){
//TODO:
return 'get project id'
}
}
}
And right part of Navbar with logout button
export default {
name: "NavbarRight",
methods:{
logOut(){
this.$store.dispatch('logOutUser').then(()=>{
this.$router.push('/');
});
},
}
}
So the problem is really stupid.
In User component data missed return.
After adding
data(){
return {}
},
Everything started working

Nuxt asyncData result is undefined if using global mixin head() method

I'm would like to get titles for my pages dynamically in Nuxt.js in one place.
For that I've created a plugin, which creates global mixin which requests title from server for every page. I'm using asyncData for that and put the response into storage, because SSR is important here.
To show the title on the page I'm using Nuxt head() method and store getter, but it always returns undefined.
If I place this getter on every page it works well, but I would like to define it only once in the plugin.
Is that a Nuxt bug or I'm doing something wrong?
Here's the plugin I wrote:
import Vue from 'vue'
import { mapGetters } from "vuex";
Vue.mixin({
async asyncData({ context, route, store, error }) {
const meta = await store.dispatch('pageMeta/setMetaFromServer', { path: route.path })
return {
pageMetaTitle: meta
}
},
...mapGetters('pageMeta', ['getTitle']),
head() {
return {
title: this.getTitle, // undefined
// title: this.pageMetaTitle - still undefined
};
},
})
I would like to set title in plugin correctly, now it's undefined
Update:
Kinda solved it by using getter and head() in global layout:
computed: {
...mapGetters('pageMeta', ['getTitle']),
}
head() {
return {
title: this.getTitle,
};
},
But still is there an option to use it only in the plugin?
Update 2
Here's the code of setMetaFromServer action
import SeoPagesConnector from '../../connectors/seoPages/v1/seoPagesConnector';
const routesMeta = [
{
path: '/private/kredity',
dynamic: true,
data: {
title: 'TEST 1',
}
},
{
path: '/private/kreditnye-karty',
dynamic: false,
data: {
title: 'TEST'
}
}
];
const initialState = () => ({
title: 'Юником 24',
description: '',
h1: '',
h2: '',
h3: '',
h4: '',
h5: '',
h6: '',
content: '',
meta_robots_content: '',
og_description: '',
og_image: '',
og_title: '',
url: '',
url_canonical: '',
});
export default {
state: initialState,
namespaced: true,
getters: {
getTitle: state => state.title,
getDescription: state => state.description,
getH1: state => state.h1,
},
mutations: {
SET_META_FIELDS(state, { data }) {
if (data) {
Object.entries(data).forEach(([key, value]) => {
state[key] = value;
})
}
},
},
actions: {
async setMetaFromServer(info, { path }) {
const routeMeta = routesMeta.find(route => route.path === path);
let dynamicMeta;
if (routeMeta) {
if (!routeMeta.dynamic) {
info.commit('SET_META_FIELDS', routeMeta);
} else {
try {
dynamicMeta = await new SeoPagesConnector(this.$axios).getSeoPage({ path })
info.commit('SET_META_FIELDS', dynamicMeta);
return dynamicMeta && dynamicMeta.data;
} catch (e) {
info.commit('SET_META_FIELDS', routeMeta);
return routeMeta && routeMeta.data;
}
}
} else {
info.commit('SET_META_FIELDS', { data: initialState() });
return { data: initialState() };
}
return false;
},
}
}

VueJS - vue-charts.js

I am trying to pass data I fetch from API to vue-chartjs as props, I am doing as in the documentation but it does not work.
Main component
<monthly-price-chart :chartdata="chartdata"/>
import MonthlyPriceChart from './charts/MonthlyPriceChart'
export default {
data(){
return {
chartdata: {
labels: [],
datasets: [
{
label: 'Total price',
data: []
}
]
},
options: {
responsive: true,
maintainAspectRatio: false
}
}
},
components: {
MonthlyPriceChart
},
created() {
axios.get('/api/stats/monthly')
.then(response => {
let rides = response.data
forEach(rides, (ride) => {
this.chartdata.labels.push(ride.month)
this.chartdata.datasets[0].data.push(ride.total_price)
})
})
.catch(error => {
console.log(error)
})
}
}
In response I have an array of obejcts, each of which looks like this:
{
month: "2018-10",
total_distance: 40,
total_price: 119.95
}
Then I want to send the data somehow to the chart so I push the months to chartdata.labels and total_price to chartdata.datasets[0].data.
chart component
import { Bar } from 'vue-chartjs'
export default {
extends: Bar,
props: {
chartdata: {
type: Array | Object,
required: false
}
},
mounted () {
console.log(this.chartdata)
this.renderChart(this.chartdata, this.options)
}
}
console.log(this.chartdata) outputs my chartsdata object from my main component and the data is there so the data is passed correctly to chart but nothing is rendered on the chart.
The documentation says this:
<script>
import LineChart from './LineChart.vue'
export default {
name: 'LineChartContainer',
components: { LineChart },
data: () => ({
loaded: false,
chartdata: null
}),
async mounted () {
this.loaded = false
try {
const { userlist } = await fetch('/api/userlist')
this.chartData = userlist
this.loaded = true
} catch (e) {
console.error(e)
}
}
}
</script>
I find this documentation a bit vague because it does not explain what I need to pass in chartdatato the chart as props. Can you help me?
Your issue is that API requests are async. So it happens that your chart will be rendered, before your API request finishes. A common pattern is to use a loading state and v-if.
There is an example in the docs: https://vue-chartjs.org/guide/#chart-with-api-data