How to pass state between components? - vue.js

I have a component called "item-detail" that has a "item" prop on it, like so:
<item-detail v-ref="itemDetail" v-if="showItemDetail" v-on:clicked="showItemDetail = false"></item-detail>
Then I have an item view component like this:
<item-view v-on:click="onItemClick(this)" title="head" :item="equipment.head"></item-view>
I'm trying to make it so that when the item-view click event fires, it passes that views "item" prop over to the item-detail component. So my onItemClick looks like this:
onItemClick: function(item) {
this.$refs.itemDetail.item = item;
appState.showItemDetail = true;
}
I can't see from the docs how to get a handle to the item-view inside that v-on:click attribute. "this" always equates to the Vue app instance inside the onItemClick method and "item" is also set to the Vue app instance.
Basically the use case is "When the item view is clicked, pass its 'item' property value to the item-detail component and display the item-detail component.".

After having a conversation I proposed that using Vuex was more suitable that passing logic up the chain which could lead to lots of scaling problems later in the project life cycle.
https://github.com/LiamDotPro/VuexStoreExample
This example shows how you can use a store to easily pass logic between components without direct relationships or chaining.
store
/* eslint-disable space-before-function-paren */
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
titleText: "hmm.."
}
const actions = {
changeHomeText: ({commit}, context) => {
commit('UPDATE_TEXT', context)
}
}
const mutations = {
UPDATE_TEXT(state, text) {
state.titleText = text
}
}
const getters = {}
export default new Vuex.Store({
strict: true,
state,
getters,
actions,
mutations
})
app
<template>
<div id="app">
<router-view/>
<div>
<h1>{{getTitle}}</h1>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
msg: '',
}
},
computed: {
getTitle: function () {
return this.$store.state.titleText;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Inner component
<template>
<div class="innerTile">
<h1>Inner Tile</h1>
<button #click="ChangeTileText()">inner tile button..</button>
</div>
</template>
<script>
export default {
name: '',
data() {
return {
msg: '',
}
},
methods: {
ChangeTileText: function () {
this.$store.dispatch("changeHomeText", "Hi from inner tile..");
}
}
}
</script>
<style scoped>
</style>

Related

New Router page doesn't load the component until manually reloading the entire page

I added a new route, and added an animation in transition to the new route.
I added the following code which pushes the new route (/first) when a button is clicked:
/* From the Template */
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition name="route1" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
/* From the Script */
methods: {
onClickTransition() {
this.$router.push("/first");
},
Now the problem is that when I click the button and invoke the "onClickTransition" method, the router seems to be pushed just fine, but the page is empty. The components are rendered only when I manually refresh the page by pressing ctrl+R.
I believe the problem perhaps comes from insertion of the animation, but if I refresh the page manually, the animation works just fine. So I have no idea what the problem is. I will be very grateful for help.
Here is the rest of the code for app.vue:
<template>
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition :key="$route.fullPath" name="route1" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {};
},
methods: {
onClickTransition() {
this.$router.push("/first");
},
leave(event) {
event.preventDefault();
event.returnValue = "";
},
},
mounted() {
window.addEventListener("beforeunload", this.leave);
},
beforeUnmount() {
window.removeEventListener("beforeunload", this.leave);
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #fff;
background-color: #151515;
position: relative;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0px;
}
/* route transition */
.route1-enter-from {
opacity: 0;
}
.route1-enter-active {
transition: all 3s ease-in;
}
.route1-leave-to {
opacity: 0;
}
.route1-leave-active {
transition: all 3s ease-in;
}
</style>
code section from index.js:
import { createRouter, createWebHistory } from "vue-router";
import MainPage from "../views/MainPage.vue";
import FirstScene from "../views/FirstScene.vue";
const routes = [
{
path: "/",
name: "main",
component: MainPage,
},
{
path: "/first",
name: "first",
component: FirstScene,
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
The "onClickTransition" method comes from the "PreStartPage.vue" component, which is the child component of "MainPage.vue" which is the main route.
Once the "Next" button is clicked in the "PreStartPage.vue", it sends an event to the "MainPage.vue" with this.$emit."MainPage.vue" then receives the event with a method named "onClickNext1", which sends out a signal to "App.vue" with another this.$emit. That is where the "#clickedNext1" that is shown in the App.vue comes from.
Here is the code from "PreStartPage.vue":
<script>
export default {
name: "PreStartPage",
methods: {
onClickNext() {
this.$emit("clickedNext");
},
},
};
</script>
Here is the code from "MainPage.vue":
<script>
import PreStartPage from "../components/PreStartPage.vue";
export default {
name: "MainPage",
components: { PreStartPage },
data() {
return { showMain: true, showPre: false };
},
methods: {
toggleMain() {
this.showMain = !this.showMain;
this.showPre = !this.showPre;
},
onClickNext1() {
this.$emit("clickedNext1");
},
},
};
</script>
Try modifying your code like this:
/* From the Template */
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition :key="$route.fullPath" name="route1" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
The "key" property set to $route.fullPath should ensure that the transition is done correctly whenever the route is changed.
EDIT
To solve this, you can add a ":enter-active-class" and ":leave-active-class" property to the transition component, which allows you to specify the class that should be applied to the element during the transition.
In your App.vue component, you can update the transition component like this:
<transition :key="$route.fullPath" name="route1" mode="out-in" :enter-active-class="'route1-enter-active'" :leave-active-class="'route1-leave-active'">
<component :is="Component" />
</transition>
This will ensure that the correct classes are applied to the element during the transition, and that the components are fully rendered before the animation starts.
For more info i should you to visit the official wiki: https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
You can try using
created() {
this.$watch(() => this.$route.params, () => {
// WATCH FOR ROUTE CHANGES
},
}

TypeError: this.$store is undefined

I'm following a Vue 2 tutorial but got stuck by this error. I've checked multiple questions before posting this one because I'm still unsure of what did I do wrong. Any input will be appreciated!
App.vue code:
<template>
<div id="app">
<!-- <img alt="Vue logo" src="./assets/logo.png"> -->
<!-- <HelloWorld msg="Welcome to Your Vue.js App"/> -->
<TodoApp />
</div>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
import TodoApp from './components/TodoApp.vue';
export default {
name: 'App',
components: {
// HelloWorld,
TodoApp
}
}
</script>
body {
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif;
line-height: 1.6;
background: #e8f7f0;
}
.container {
max-width: 1100px;
margin: auto;
overflow: auto;
padding: 0 2rem;
}
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Store (index.js) code:
import Vuex from 'vuex';
import Vue from 'vue';
import TodoModule from './modules/TodoModule.js';
// Load vuex
Vue.use(Vuex);
// Create store
export default new Vuex.Store({
modules: {
TodoModule
}
});
Main.js code:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App),
}).$mount('#app')
TodoApp.vue code:
<template>
<div>
<h3>Vuex Todo App</h3>
<div class="todos">
<div class="todo" v-for="todo in allTodos" :key="todo.id">
{{ todo.title }}
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: "TodoApp",
computed: mapGetters(['allTodos'])
};
</script>
<style>
</style>
TodoModule.js code:
// import axios from "axios";
const state = {
todos: [
{
id: 1,
title: "first todo"
},
{
id: 2,
title: "second todo"
},
{
id: 3,
title: "third todo"
}
]
};
const getters = {
allTodos: state => state.todos
};
const actions = {};
const mutations = {};
export default {
state,
getters,
actions,
mutations
}
No Error on Compiler:
Error on Browser Console:
=== UPDATE ===
I still haven't fix this issue and I have tried restarting the localhost server, even my own laptop to check if it'll fix it.. no luck there.
From what I've read from answers, is it possible that this error happen because of my vue installation? Or perhaps I forgot to install something else when starting the development?
=== UPDATE 2 ===
Found the culprit, apparently my vue and vuex npm package either got corrupted or has a conflict, changing the version of vue to 2.7.8 and vuex to 3.0.1 then rerun npm i (after deleting the node_modules and package-lock.json) has fixed the issue!
Thanks for all the help!
I got the same problem while using Vue 2.
npm uninstall vuex
then
npm i vuex#3.0.1
npm install vuex by default npm installs version 4 which is not supported by Vue 2
https://github.com/vuejs/vuex/releases
Your code seems ok.
I could reproduce with no erros using TodoModule below.
const TodoModule = {
state: {
todos: [{ title: "1" }, { title: "2" }, { title: "3" }, { title: "4" }]
},
getters: {
allTodos: (state) => state.todos
}
};
export default TodoModule;
If error continues, you could check the naming on your files.
The store index.js as you said should be named as store.js.
Or perhaps restart server.
I think I might have actually done the same youtube tutorial on vuex state management but I didn't use the mapGetter helper function, instead, I used the vanilla functions of vuex for getting and setting global variables.
main.js
store: new vuex.Store({
state: {
user: {}
},
mutations: {
setUser(state, payload){
state.user = payload;
}
},
getters: {
getUser: state => state.user,
}})
any-component.js
this.user = this.$store.getters.getUser; //get
this.$store.commit("setUser", this.user); //set
I think you should change getter inTo
export default {
name: "TodoApp",
computed: mapGetters(['allTodos'])
};
===>
export default {
name: "TodoApp",
computed: {
...mapGetters(['allTodos'])
}
};

How to import a Vue component that is packaged into a JavaScript library?

I am attempting to package a Vue component into a JavaScript library and then use it in another project using vue-sfc-rollup.
I am able to package the component just as the README says to do. Then when I copy the .min.js file into another project and attempt to use the component, I always get the error:
Vue warn handler: Failed to mount component: template or render function not defined.
The way I'm trying to use the component from inside another Vue component is this:
import Vue from 'vue'
import MyComponent from '../lib/my-components.min.js'
Vue.use(MyComponent)
Then in the components section:
components: {
'my-component': MyComponent
}
Then in the template:
<my-component></my-component>
What am I missing here? What is the correct way to use the component in another project?
EDIT: Adding component code in response to comment.
<template>
<div class="my-component">
<p>The counter was {{ changedBy }} to <b>{{ counter }}</b>.</p>
<button #click="increment">
Click +1
</button>
<button #click="decrement">
Click -1
</button>
<button #click="increment(5)">
Click +5
</button>
<button #click="decrement(5)">
Click -5
</button>
<button #click="reset">
Reset
</button>
</div>
</template>
<script>
export default {
name: 'MyComponent', // vue component name
data() {
return {
counter: 5,
initCounter: 5,
message: {
action: null,
amount: null,
},
};
},
computed: {
changedBy() {
const {
message
} = this;
if (!message.action) return 'initialized';
return `${message?.action} ${message.amount ?? ''}`.trim();
},
},
methods: {
increment(arg) {
const amount = (typeof arg !== 'number') ? 1 : arg;
this.counter += amount;
this.message.action = 'incremented by';
this.message.amount = amount;
},
decrement(arg) {
const amount = (typeof arg !== 'number') ? 1 : arg;
this.counter -= amount;
this.message.action = 'decremented by';
this.message.amount = amount;
},
reset() {
this.counter = this.initCounter;
this.message.action = 'reset';
this.message.amount = null;
},
},
};
</script>
<style scoped>
.my-component {
display: block;
width: 400px;
margin: 25px auto;
border: 1px solid #ccc;
background: #eaeaea;
text-align: center;
padding: 25px;
}
.my-component p {
margin: 0 0 1em;
}
</style>
I found one way to do this at this Stack Overflow question: Register local Vue.js component dynamically.
I got it to work by implementing a simpler version of the solution shown there. I removed the component section from the outer component, then added this created() lifecycle hook:
created() {
console.log('pages/PageHome.vue: created(): Fired!')
// From https://stackoverflow.com/questions/40622425/register-local-vue-js-component-dynamically
// "This is how I ended up importing and registering components dynamically to a component locally"
const componentConfig = require('../lib/components/my-component.js')
console.log('pages/PageHome.vue: created(): componentConfig.default = ')
console.log(componentConfig.default)
const componentName = 'my-component'
console.log('pages/PageHome.vue: componentName = ' + componentName)
this.$options.components[componentName] = componentConfig.default
}
The component is imported using a require() call, then registered locally by adding it to the this.$options.components dictionary. The secret sauce is to add .default to the componentConfig expression. This doesn't seem to be formally documented anywhere.
Editorial comment: I'm surprised the Vue documentation pays such little attention to distribution patterns for re-usability. As great as the Vue docs are, this is a glaring omission.

How to update a Single File Component's data with its method?

I'm in the process of learning Vue.js and now I'm learning about Single File Component. My objective is to ask user for their location through Geolocation API and then update the page with their latitude and longitude coordinate values. I'm able to get the coords values through console.log, but I can't get the SFC to update itself with the values. I may be missing something here.
geolocation.vue
<template>
<p>Your location is: {{ text }}. Is this correct?</p>
</template>
<script>
console.log('in geolocation.vue');
let text = "N/A";
export default {
name: "geolocation",
data: function () {
return {
text: text,
}
},
methods: {
findLocation: function() {
let that = this;
console.log('in findLocation()');
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (pos) {
console.log('CONSOLE: ' + pos.coords.latitude + ', ' + pos.coords.longitude);
that.text = pos.coords.latitude + ', ' + pos.coords.longitude;
});
}
}
}
}
</script>
<style scoped>
p {
color: red;
font-size: 85%;
}
</style>
App.vue
<template>
<div class="center-text">
<h1 class="site-title">{{ app_name }}</h1>
<p>I'm the store page!</p>
<p>Soon I'll display products according to the inventory at the distribution center(s) in the area.</p>
<geolocation/>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
import geolocation from './geolocation';
export default {
computed: {
...mapGetters(['app_name'])
},
components: {
geolocation
},
mounted: function() {
geolocation.methods.findLocation();
}
};
</script>
<style scoped>
.site-title {
font-family: 'Lobster', cursive;
}
.center-text {
text-align: center;
}
</style>
Add this to the geolocation.vue
mounted() {
this.findLocation();
}
Remove these lines in App.vue
mounted: function() {
geolocation.methods.findLocation();
}
Split your component into many child component and it has cycle hooks its self.
When geolocation component has been mounted, findLocation will be called and the location will be bind into the template.

Pass iterable element as function parameter in v-for loop with Vue

I'm working with Vuex and in one of my components, I try to pass an iterable element as function parameter in buttons v-for loop. My problem is that instead of getting the element I want, I get an empty object...
I would also like to know if I'm passing the parameter to the store actions the right way?
Code goes as follow:
//Side_bar.vue
<template>
<div id="sideBar">
<ul>
<li v-for='l in links'>
<button v-on:click='d(l.title)'>{{l.title}}</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'sideBar',
data () {
return {
links: [
{'title':'asset', 'valuesss':'ASSET'},
{'title':'task', 'valuesss':'TASK'},
{'title':'user', 'valuesss':'USER'}
]
}
},
computed:{
d(v){
console.log(v)
// update active table
this.$store.dispatch('updateActiveTable',v)
}
}
}
</script>
<style>
li {
list-style-type: none;
padding-bottom: 5px;
}
</style>
store file looks like this
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
activeTable: 'assets' // def view
};
const mutations = {
setActiveTable(context,v){
context.activeTable = v
}
};
const getters={
getActiveTable(context){
//return active table
return context.activeTable
}
};
const actions={
updateActiveTable(context,v){
console.log(context)
context.commit('setActiveTable',v)
}
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
App.vue looks like that
<template>
<div id="app">
<sideBar></sideBar>
<tableComponent></tableComponent>
</div>
</template>
<script>
import sideBar from './components/Side_bar'
import tableComponent from './components/Table_component'
export default {
name: 'app',
components:{
sideBar,
tableComponent
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
small {
display: block;
font-style: italic;
}
</style>
The code defines d as a computed property.
It should be a method.
methods:{
d(v){
console.log(v)
// update active table
this.$store.dispatch('updateActiveTable',v)
}
}