Vue JS : Can not update dom from created function - vue.js

I created my project by
vue init webpack project
#vue/cli 4.0.5
Here is my App.vue.
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
Router file
let router = new Router({
routes: [
{
path: '/videos',
name: 'Videos',
component: Videos
}
]
})
Files under Videos folder
index.js
import Videos from './Videos'
export default Videos
Videos.vue
<template>
<div>
<ul>
<li v-for="video in videos" :key="video.index">
{{ video.index }} - {{ video.value }}
</li>
</ul>
<div class="button">
<cv-button #click="submit">Submit</cv-button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
created: () => {
const _this = this
const url = process.env.API_URL
axios.get(url + 'api/hello', {mode: 'no-cors'})
.then(response => {
const resource = response.data
const videos = resource.videos
_this.videos = Object.keys(videos).map((key) => {
return {
index: key,
value: videos[key]
}
})
})
},
data: () => {
return {
videos: []
}
},
methods: {
submit: function () {
const url = process.env.API_URL
axios.get(url + 'api/videos')
.then(response => {
console.log(response)
const resource = response.data
const videos = resource.videos
this.videos = Object.keys(videos).map((key) => {
return {
index: key,
value: videos[key]
}
})
})
}
}
}
</script>
Basically, I want to get a list of videos inside created function but neither this.videos nor _this.videos worked. When I tried to log this inside the created function, I was seeing a {} JSON object, not VueComponent.
{
a: {computed: {}, data: f, ...},
videos: [{...},{...}]
}
When I tried to get the list by click on the button, which triggers the submit function, it worked as expected, and this was a VueComponent.
VueComponent {_uid: 23, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
I don't understand what happened here? Why I worked with the submit function but not inside the created function?
Thanks

With created: () => {} notation created function executes in global scope. Try created() {}

Related

Vue dynamically adding routes from wordpress menus api, possible reaactivity problem

I have a vue application on the frontend and a wordpress api on the backend. I am hitting the menus api and dynamically adding routes to the frontend at run time.
This works great. Until I reset the page on one of the dynamic routes. The component does not load and mounted() is never called. At this point, I can click the router link in the nav bar and the page component renders as expected.
For example. In the wordpress admin, I create a page called hello-world and add it to the primary menu. Vue will hit the api and create a route with the same name. I then load up the page and it loads fine. I click the hello world link in the nav bar, and it renders beautifully.
Now, I'm sitting at http://website.com/hello-world, and I reset the page. The app mounts and the nav bar renders. However, the page component does not render. If I click the link in the nav bar again, then it renders fine.
I am thinking this is a reactivity problem, but I can't find it. Any suggestions?
Edit. Been pondering this. The router component is loaded, and fetches the menu items asynchronously. Now, Im already sitting on one of the dynamic routes, /hello-world. The app is now loaded and there doesn't exist yet a hello-world route, since the api request is still pending. Since there is no matching route, the vue application doesn't know which component to mount... Perhaps there is a way to make the router component itself reactive?
relevant router code...
store.dispatch("getPrimaryMenu").then(() => {
store.state.menu.items.forEach((item) => {
if (item.object === "post") {
router.addRoute({
path: `/${item.slug}`,
name: item.slug,
component: () => import("#/views/Post.vue"),
});
}
if (item.object === "page") {
router.addRoute({
path: `/${item.slug}`,
name: item.slug,
component: () => import("#/views/Page.vue"),
});
}
});
});
and my store...
export default createStore({
state: {
menu: {
items: [],
},
page: {
title: {},
content: {},
},
post: {
title: {},
content: {},
},
},
mutations: {
SET_MENU(state, data) {
state.menu = data
},
SET_PAGE(state, data) {
state.page = data
},
SET_POST(state, data) {
state.post = data
},
},
actions: {
getPrimaryMenu({ commit, state }) {
console.log('get menus')
return new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`http://sslchkr.com/wp-json/menus/v1/menus/primary`, {
headers: {
'Content-Type': 'application/json'
}
}
)
commit('SET_MENU', data)
resolve(data)
} catch (e) {
reject(e)
}
})
},
getPage({ commit, state }, payload) {
console.log('get page')
return new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`http://sslchkr.com/wp-json/wp/v2/pages/${payload.id}`, {
headers: {
'Content-Type': 'application/json'
}
}
)
commit('SET_PAGE', data)
resolve(data)
} catch (e) {
reject(e)
}
})
},
getPost({ commit, state }, payload) {
console.log('get post')
return new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`http://sslchkr.com/wp-json/wp/v2/posts/${payload.id}`, {
headers: {
'Content-Type': 'application/json'
}
}
)
commit('SET_POST', data)
resolve(data)
} catch (e) {
reject(e)
}
})
},
},
}
a page component...
I am matching the route name to an item slug from the menu object, and using that item object_id to fetch the page object.
<template>
<div class="page">
<div>
<h1>{{ page.title.rendered }}</h1>
</div>
<div v-html="page.content.rendered"></div>
</div>
</template>
<script>
export default {
name: "Page",
computed: {
menuItem() {
return this.$store.state.menu.items.find(
(item) => item.slug === this.$route.name
);
},
page() {
return this.$store.state.page;
},
},
mounted() {
this.$store.dispatch("getPage", { id: this.menuItem.object_id });
},
};
</script>
and the nav component for completeness...
<template>
<ul id="menu-primary list-inline">
<li
v-for="item in menu.items"
:key="item.ID"
class="nav-item list-inline-item"
>
<router-link :to="slash(item.slug)" class="nav-link">{{
item.title
}}</router-link>
</li>
</ul>
</template>
<script>
export default {
name: "Nav",
computed: {
menu() {
return this.$store.state.menu;
},
},
methods: {
slash(s) {
return `/${s}`;
},
},
};
</script>
Edit to include main.js and App.vue
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/js/bootstrap.js'
import 'vue-toastification/dist/index.css'
import { createApp } from 'vue'
import Toast, { POSITION } from 'vue-toastification'
import App from './App.vue'
import router from './router'
import store from './store'
let app = createApp(App)
app.use(store)
app.use(router)
app.use(Toast, { position: POSITION.TOP_CENTER })
app.mount('#app')
<template>
<link rel="stylesheet" :href="theme" />
<Nav />
<div class="container-fluid">
<div class="row padding-top">
<div class="col-md-2"></div>
<div class="col-md-8">
<router-view :key="$route.path" />
</div>
<div class="col-md-2"></div>
</div>
</div>
</template>
<script>
import Nav from "#/components/Nav.vue";
export default {
components: {
Nav,
},
computed: {
theme() {
return this.$store.state.theme;
},
},
mounted() {
this.$store.dispatch("getTheme");
},
};
</script>

How to get vuex pagination to be reactive?

I am not sure what the problem is. I am creating a Vue/Vuex pagination system that calls my api to get my list of projects. The page initially loads all the projects in when the page is mounted. The Vuex does the inital call with axios. Vuex finds the projects and the number of pages. Once the user clicks on the pagination that is created with the pagination component, it should automatically change the projects for page 2...3 etc.
The problem I have is that it is not reactive until you press the page number twice. I am using vue 3. I have tried not using Vuex and that was successful. I am trying to create a single store that does all the axios calls. Thanks for all your help!
Vuex store
import { createStore } from 'vuex';
import axios from 'axios';
/**
* Vuex Store
*/
export const store = createStore({
state() {
return {
projects: [],
pagination: {}
}
},
getters: {
projects: state => {
return state.projects;
}
},
actions: {
async getProjects({ commit }, page = 1) {
await axios.get("http://127.0.0.1:8000/api/guest/projects?page=" + page)
.then(response => {
commit('SET_PROJECTS', response.data.data.data);
commit('SET_PAGINATION', {
current_page: response.data.data.pagination.current_page,
first_page_url: response.data.data.pagination.first_page_url,
prev_page_url: response.data.data.pagination.prev_page_url,
next_page_url: response.data.data.pagination.next_page_url,
last_page_url: response.data.data.pagination.last_page_url,
last_page: response.data.data.pagination.last_page,
per_page: response.data.data.pagination.per_page,
total: response.data.data.pagination.total,
path: response.data.data.pagination.path
});
})
.catch((e) => {
console.log(e);
});
}
},
mutations: {
SET_PROJECTS(state, projects) {
state.projects = projects;
},
SET_PAGINATION(state, pagination) {
state.pagination = pagination;
}
},
});
Portfolio Component
<template>
<div>
<div id="portfolio">
<div class="container-fluid mt-5">
<ProjectNav></ProjectNav>
<div class="d-flex flex-wrap overflow-auto justify-content-center mt-5">
<div v-for="project in projects" :key="project.id" class="m-2">
<Project :project="project"></Project>
</div>
</div>
</div>
</div>
<PaginationComponent
:totalPages="totalPages"
#clicked="fetchData"
></PaginationComponent>
</div>
</template>
<script>
import Project from "../projects/Project.vue";
import PaginationComponent from "../pagination/PaginationComponent.vue";
import ProjectNav from "../projectNav/ProjectNav.vue";
/**
* PortfolioComponent is where all the projects are displayed.
*/
export default {
name: "PortfolioComponent",
data() {
return {
location: "portfolio",
projects: []
};
},
components: {
Project,
PaginationComponent,
ProjectNav,
},
mounted() {
this.fetchData(1);
},
computed: {
totalPages() {
return this.$store.state.pagination.last_page;
},
},
methods: {
fetchData(page) {
this.$store.dispatch("getProjects", page);
this.projects = this.$store.getters.projects;
},
},
};
</script>
In your fetchData method you are calling the async action getProjects, but you are not waiting until the returned promise is resolved.
Try to use async and await in your fetchData method.
methods: {
async fetchData(page) {
await this.$store.dispatch("getProjects", page);
this.projects = this.$store.getters.projects;
},
},

Props arguments are not reactive in setup

I'm trying to develop a component that compiles the given html; I succeeded with constant html texts, but I can't make this work with changing html texts.
main.js
app.component("dyno-html", {
props: ["htmlTxt"],
setup(props) {
watchEffect(() => {
console.log(`htmlText is: ` + props.htmlTxt);
return compile(props.htmlTxt);
});
return compile(props.htmlTxt);
},
});
Home.js
<template>
<div class="home">
<dyno-html
:htmlTxt="html2"
:bound="myBoundVar"
#buttonclick="onClick"
></dyno-html>
-------------
<dyno-html
:htmlTxt="html"
:bound="myBoundVar"
#buttonclick="onClick"
></dyno-html>
</div>
</template>
<script>
export default {
name: "Home",
components: {},
data: function() {
return {
html: "",
html2: `<div> Static! <button #click="$emit('buttonclick', $event)">CLICK ME</button></div>`
};
},
mounted() {
// get the html from somewhere...
setTimeout(() => {
this.html = `
<div>
Dynamic!
<button #click="$emit('buttonclick', $event)">CLICK ME</button>
</div>
`;
}, 1000);
},
methods: {
onClick(ev) {
console.log(ev);
console.log("You clicked me!");
this.html2 = "<b>Bye Bye!</b>";
},
},
};
</script>
Outcome:
Console:
It seems the changes of htmlText arrives to setup function, but it doesn't affect the compile function!
This is the expected behaviour because prop value is read once and results in static render function.
Prop value should be read inside render function. It can be wrapped with a computed to avoid unneeded compiler calls:
const textCompRef = computed(() => ({ render: compile(props.htmlTxt) }));
return () => h(textCompRef.value);

How make json data available for my Vue dynamic routes

I have a List component where I fetch my date from db/blogs.json:
created() {
fetch('http://localhost:3000/blogs')
.then(response => {
return response.json();
})
.then(data => {
this.blogs = data;
})
},
In my BlogDetail.vue I have:
<script>
export default {
data: () => {
return {
blogId:this.$route.params.id
}
},
computed: {
blog() {
return this.blogs.find(
blog => blog.id === this.blogId
)
}
}
}
</script>
But how do I get the blogs data in this component, which I fetched in the List component?
Because now in the <template> section of my BlogDetail.vue I cannot access e.g. {{ blog.name }}
Update:
I try passing blogs with props:
Now I am accepting a prop in BlogDetails.vue:
props: {
blogs: {
type: Array
}
},
But from where (which component), I have to registering the prop like :blogs="blogs"?
Update 2:
This is what I have so far, link to the sandbox
Here is the working sandbox.
Firstly you need to import JSON data from your JSON file correctly. As:
<script>
import ListItem from "./ListItem";
import Blogs from "../../public/db/blogs.json";
export default {
name: "List",
components: {
ListItem
},
data() {
return {
blogs: Blogs.experiences
};
},
created() {}
};
</script>
Have to send props in the router-link as :
<router-link
:to="{ name: 'BlogDetails', params: { id: blog.id,blog:blog }}">More information
</router-link>
You can send props to the child component in the tag name, in your case:
//LIST component(PARENT)
<tamplate>
<BlogDetail :blogs="blogs"></BlogDetail> //CHILD component
</template>

Nuxt.js / Vuex - using Namespace modules in mapActions helper, [vuex] unknown action type: FETCH_LABEL

I am trying to map an action to a component using mapActions helper from vuex. Here is my labels.js vuex module:
export const FETCH_LABELS = 'FETCH_LABELS'
export const FETCH_LABEL = 'FETCH_LABEL'
const state = () => ({
labels: [
{ name: 'Mord Records', slug: 'mord', image: '/images/labels/mord.jpg'},
{ name: 'Subsist Records', slug: 'subsist', image: '/images/labels/subsist.jpg'},
{ name: 'Drumcode Records', slug: 'drumcode', image: '/images/labels/drumcode.png'},
],
label: {} // null
})
const mutations = {
FETCH_LABEL: (state, { label }) => {
state.label = label
},
}
const actions = {
fetchLabel({commit}, slug) {
let label = state.labels.filter((slug, index) => {
return slug == state.labels[index]
})
commit(FETCH_LABEL, { label })
},
}
const getters = {
labels: state => {
return state.labels
},
label: (state, slug) => {
}
}
export default {
state,
mutations,
actions,
getters
}
Here is my component _slug.vue page where I want to map the fetchLabel action:
<template>
<div class="container">
<div class="section">
<div class="box">
<h1>{{ $route.params.slug }}</h1>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
title: this.$route.params.slug
};
},
computed: {
// Research
// labels() {
// return this.$store
// }
...mapGetters({
labels: "modules/labels/labels"
})
},
components: {},
methods: {
...mapActions({
fetchLabel: 'FETCH_LABEL' // map `this.add()` to `this.$store.dispatch('increment')`
})
},
created() {
console.log('created')
this.fetchLabel(this.$route.params.slug)
},
head() {
return {
title: this.title
}
},
layout: "app",
}
</script>
<style>
</style>
However inside the created() lifecycle hook at this.fetchLabel(this.$route.params.slug) it throws the following error in the console:
[vuex] unknown action type: FETCH_LABEL
What am I missing or doing wrong? Please help me solve this.
Note that in Nuxt.js:
Modules: every .js file inside the store directory is transformed as a namespaced module (index being the root module).
You are using:
Here is my labels.js vuex module:
with labels.js as you stated above so you'll need to access everything as namespaced modules so your mapAction helper should be like as such:
methods: {
...mapActions({
nameOfMethod: 'namespace/actionName'
})
}
So you would have this:
...mapActions({
fetchLabel: 'labels/fetchLabel'
})
You could also clean it up by doing so for when you'd like to retain the name of your action as your method name.
...mapActions('namespace', ['actionName']),
...
So you would have this:
...mapActions('labels', ['fetchLabel']),
...
In both cases the computed prop should work without a problem.
Your action name is fetchLabel and not FETCH_LABEL (which is a mutation). In mapActions change to
methods: {
...mapActions({
fetchLabel
})
},