Remove Item from Firebase Vuex - javascript

I'm new to Vue and I wanted to learn Veux by building a simple CRUD application with firebase. So far I've been able to figure things out (though if you see something badly coded, I would appreciate any feedback) but I can't seem to figure out how to remove an item. The main problem is that I can't reference it properly. I'm getting [object Object] in my reference path but when I log it I get the correct id.
Firebase Flow:
So I'm making a reference to 'items', Firebase generates a unique key for each item and I added in an id to be able to reference it, though I could also reference it by the key.
I've been able to do this without using Veux and just component state but I've been trying for hours to figure out what I'm doing wrong.
I'm also getting this error:
Store.js
import Vue from 'vue'
import Vuex from 'vuex'
import database from './firebase'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
items: []
},
mutations: {
RENDER_ITEMS(state) {
database.ref('items').on('value', snapshot => {
state.items = snapshot.val()
})
},
ADD_ITEM(state, payload) {
state.items = payload
database.ref('items').push(payload)
},
REMOVE_ITEM(index, id) {
database.ref(`items/${index}/${id}`).remove()
}
},
// actions: {
// }
})
Main.vue
<template>
<div class="hello">
<input type="text" placeholder="name" v-model="name">
<input type="text" placeholder="age" v-model="age">
<input type="text" placeholder="status" v-model="status">
<input type="submit" #click="addItem" />
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.name }}
{{ item.age }}
{{ item.status }}
<button #click="remove(index, item.id)">Remove</button>
</li>
</ul>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import uuid from 'uuid'
export default {
name: 'HelloWorld',
created() {
this.RENDER_ITEMS(this.items)
},
data() {
return {
name: '',
age: '',
status: '',
id: uuid(),
}
},
computed: {
...mapState([
'items'
])
},
methods: {
...mapMutations([
'RENDER_ITEMS',
'ADD_ITEM',
'REMOVE_ITEM'
]),
addItem() {
const item = {
name: this.name,
age: this.age,
status: this.status,
id: this.id
}
this.ADD_ITEM(item)
this.name = ''
this.age = ''
this.status = ''
},
remove(index, id) {
console.log(index, id)
this.REMOVE_ITEM(index, id)
}
}
}
</script>

The first argument to your mutation is always the state.
In your initial code:
REMOVE_ITEM(index, id) {
database.ref(`items/${index}/${id}`).remove()
}
index is the state object, which is why you are getting [object Object] in the url.
To fix your issue, pass an object to your mutation and change it to:
REMOVE_ITEM(state, {index, id}) {
database.ref(`items/${index}/${id}`).remove()
}
And when you are calling your mutation with the remove method, pass an object as well:
remove(index, id) {
console.log(index, id)
// pass an object as argument
// Note: {index, id} is equivalent to {index: index, id: id}
this.REMOVE_ITEM({index, id})
}

Related

Issue when trying to interact with an API in Vuejs?

datalist.js
import axios from "axios";
export const datalist = () => {
return axios.get("myapiurl/name...").then((response) => response);
};
HelloWorld.vue
<template>
<div>
<div v-for="item in items" :key="item.DttID">
<router-link
:to="{
name: 'UserWithID',
params: { id: item.DepaD },
query: { DepaD: item.DepaID },
}"
>
<div class="bt-color">{{ item.DepaName }}</div>
</router-link>
</div>
<br /><br /><br />
<User />
</div>
</template>
<script>
import User from "./User.vue";
import { datalist } from "./datalist";
export default {
name: "HelloWorld",
components: {
User,
},
data() {
return {
items: datalist,
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
<template>
<div>
<div v-for="(item, key) in user" :key="key">
{{ item.Accv }}
</div>
</div>
</template>
<script>
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
lists: datalist,
};
},
computed: {
user: function () {
return this.lists.filter((item) => {
if (item.DepaD === this.$route.params.id) {
return item;
}
});
},
},
};
</script>
Error with the code is,
[Vue warn]: Error in render: "TypeError: this.lists.filter is not a function"
TypeError: this.lists.filter is not a function
The above error i am getting in User.vue component in the line number '20'
From the api which is in, datalist.js file, i think i am not fetching data correctly. or in the list filter there is problem in User.vue?
Try to change the following
HelloWorld.vue
data() {
return {
items: [],
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
User.vue
data() {
return {
lists: []
};
},
mounted() {
datalist().then((r) => {
this.lists = r.data;
});
},
At least this suppress the error, but i cant tell more based on your snippet since there are network issues :)
Since your datalist function returns a Promise, you need to wait for it to complete. To do this, simply modify your component code as follows:
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
// empty array on initialization
lists: [],
};
},
computed: {
user: function() {
return this.lists.filter((item) => {
if (item.DeploymentID === this.$route.params.id) {
return item;
}
});
},
},
// asynchronous function - because internally we are waiting for datalist() to complete
async-mounted() {
this.users = await datalist() // or datalist().then(res => this.users = res) - then async is not needed
}
};
now there will be no errors when initializing the component, since initially lists is an empty array but after executing the request it will turn into what you need.
You may define any functions and import them, but they wont affect until you call them, in this case we have datalist function imported in both HelloWorld and User component, but it did not been called in User component. so your code:
data() {
return {
lists: datalist,
};
},
cause lists to be equal to datalist that is a function, no an array! where .filter() should be used after an array, not a function! that is the reason of error.
thus you should call function datalist and put it's response in lists instead of putting datalist itself in lists
Extra:
it is better to call axios inside the component, in mounted, created or ...
it is not good idea to call an axios command twice, can call it in HelloWorl component and pass it to User component via props

Update State in Vuex?

I am trying to create a user Profile form where you can edit user's information such as name and age. I have 2 routes set up, the /which is for the main user component and the /edit which leads to the user Edit component. I have a user state that i am looping over to output name and age for a user in my User Component. I have added a method in my User component named enterEditMode which on click fetches name and age property of the user selected and then outputs that into the form in my EditMode Component. I am trying to create a method which onClick would update the name or age of the user. So i'd like to click on Sam and on the next page, update his name to Samuel and then click on Update Info button which should update the name Sam to Samuel on my User component page.
But i am having a hard time figuring out how i will do it.
Please check this complete working Example.
This is my Vuex Store:-
state: {
user: [{ name: "Max", age: 29 }, { name: "Sam", age: 28 }],
name: "",
age: null
},
getters: {
getUser: state => state.user,
getName: state => state.user.name,
getAge: state => state.user.age
},
mutations: {
setName(state, payload) {
state.name = payload;
},
setAge(state, payload) {
state.age = payload;
}
}
This is my User Component:-
<template>
<v-container>
<v-flex v-for="(user,index) in getUser" :key="index">
{{ user.name }} {{user.age}}
<v-icon #click="enterEditMode(index)">create</v-icon>
</v-flex>
</v-container>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
export default {
name: "User",
computed: {
...mapGetters({
getUser: "getUser"
})
},
methods: {
...mapMutations({
setName: "setName",
setAge: "setAge"
}),
enterEditMode(index) {
this.setName(this.getUser[index].name);
this.setAge(this.getUser[index].age);
this.$router.push({ name: "EditMode", params: { index: index } });
}
}
};
</script>
This is my EditMode Component:-
<template>
<v-card>
<v-text-field solo v-model="name"></v-text-field>
<v-text-field solo v-model="age"></v-text-field>
<v-btn>Update Info</v-btn>
</v-card>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
getName: "getName",
getAge: "getAge"
}),
name: {
get() {
return this.getName;
},
set(val) {
return this.$store.commit("setName", val);
}
},
age: {
get() {
return this.getAge;
},
set(val) {
return this.$store.commit("setAge", val);
}
}
},
methods: {
updateInfo() {
this.$router.push('/')
}
}
};
</script>
Thank you for all the help guys. Thank you.
You need to create a mutation in the store to update the user list. For instance to update the selected user name:
Create a updateUserName mutation, and make sure the payload contains both the user index and name to be updated:
mutations: {
updateUserName(state, payload) {
state.user[payload.index].name = payload.name;
}
}
And then in the EditMode.vue file, let the set method of computed name to commit the updateUserName mutation we just created, keep in mind to pass in both the index and name properties:
name: {
get() {
return this.getName;
},
set(val) {
return this.$store.commit("updateUserName", {
name: val,
index: this.index
});
}
}
Here index is another computed property taken from the route parameters for convenience:
index() {
return this.$route.params.index;
},
Check the CodeSandbox working example.

uuid for v-for key with vue-uuid?

I'm trying to use a random string (UUID v4) with vue-uuid for current items and items added to the list in the future (this is a to-do list type app) but I'm not sure what to correct syntax is.
I installed it and added it to my project in main.js:
import UUID from 'vue-uuid';
Vue.use(UUID);
However, I don't know how to use it in my Vue component. This is what I tried:
Template:
<transition-group
name="list"
enter-active-class="animated bounceInUp"
leave-active-class="animated bounceOutDown"
>
<li v-for="item in skills" :key="uuid">{{ item.skill }}</li>
</transition-group>
Script:
import { uuid } from 'vue-uuid';
export default {
name: 'Skills',
data() {
return {
uuid: uuid.v4(),
skill: '',
skills: [{ skill: 'Vue.js' }, { skill: 'React' }]
};
},
};
For :key="uuid", I get an error saying Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive (vue/valid-v-for). I also tried changing it to :key="item.uuid" which makes that error go away, but then the list doesn't appear.
project repo (based on this Udemy Vue crash course)
Try this:
<template>
<div id="app">
<p :key="item.uuid" v-for="item in skills">{{ item.skill }}</p>
</div>
</template>
<script>
import { uuid } from "vue-uuid";
export default {
name: "App",
data() {
return {
skills: [
{ uuid: uuid.v4(), skill: "Vue.js" },
{ uuid: uuid.v4(), skill: "React" }
]
};
}
};
</script>
This is a working demo: https://codesandbox.io/s/nifty-sutherland-b0k9q
UPDATED
to be dynamic
There are two moments that you could add the uuid to each element in the skills array:
1 When adding a new skill:
addSkill() {
this.$validator.validateAll().then(result => {
if (result) {
this.skills.push({ uuid: uuid.v4(), skill: this.skill });
this.skill = "";
}
});
}
2 When rendering them, in this case, you might use a computed property like so:
import { uuid } from 'vue-uuid';
export default {
name: 'Skills',
data () {
return {
skill: '',
skills: [{ skill: 'Vue.js' }, { skill: 'React' }]
};
},
computed: {
computedSkills () {
return this.skills.map(skill => {...skill, uuid: uuid.v4() })
}
}
};
And then using the computedSkills computed property for rendering rather than the skills property. Something like:
<li v-for="item in computedSkills" :key="item.uuid">{{ item.skill }}</li>

Problems with data communication between components

I had a page on which there was a header with an input that was a search engine, a list of posts, and pagination. I decided to move the header from this file to a separate component in a separate vue file. After I did this, the search for posts by title stopped working, and I can’t add a post now either. I think that I need to import my posts into a new file for my newly created component but how to do it.
My code when it worked(before my changes)
My code is not working after the changes:
The file in which my posts situated:
<template>
<div class="app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Pagination',
data () {
return {
search: '',
current: null,
page: 0,
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
watch: {
counter: function(newValue, oldValue) {
this.getData()
}
},
created(){
this.getData()
},
computed: {
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.posts.filter((post) => {
return post.title.match(this.search);
}).slice(start, end);
},
},
methods: {
getData() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(response => {
this.posts = response.data
})
},
}
}
</script>
Header vue:
AddPost
<script>
import axios from 'axios';
export default {
name: 'Pagination',
data () {
return {
search: '',
current: null,
posts: [],
createTitle: '',
createBody: '',
}
},
created(){
this.getData()
},
methods: {
getData() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(response => {
this.posts = response.data
})
},
addPost() {
axios.post('http://jsonplaceholder.typicode.com/posts/', {
title: this.createTitle,
body: this.createBody
}).then((response) => {
this.posts.unshift(response.data)
})
},
}
}
</script>
App.vue:
<template>
<div id="app">
<header-self></header-self>
<router-view></router-view>
</div>
</template>
<script>
export default {
components: {
name: 'app',
}
}
</script>
You have a computed property paginatedData in your "posts" component that relies a variable this.search:
paginatedData () {
const start = this.page * 6;
const end = start + 6;
return this.posts.filter((post) => {
return post.title.match(this.search);
}).slice(start, end);
},
but this.search value is not updated in that component because you moved the search input that populates that value into the header component.
What you need to do now is make sure that the updated search value is passed into your "posts" component so that the paginatedData computed property detects the change and computes the new paginatedData value.
You're now encountering the need to pass values between components that may not have a parent/child relationship.
In your scenario, I would look at handling this need with some Simple State Management as described in the Vue docs.
Depending on the scale of you app it may be worth implementing Vuex for state management.

Computed property in Vue is not triggering a watch

According to this post, it shouldn't be a problem to watch a computed property. And yet my code isn't working.
<template>
<div v-if="product" class="section">
<form>
<div class="control"><input type="text" class="input" v-model="title"></div>
<div class="control"><input type="text" class="input" v-model="description"></div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
title: null,
description: null
}
},
computed: {
product() {
// const payload = { collection: 'products', id: this.$route.params.productId }
// return this.$store.getters.objectFromId(payload)
console.log('working')
return { title: 'Awesome Title', description: 'Awesome Description' }
}
},
watch: {
product() {
this.title = this.product.title,
this.description = this.product.description
}
}
}
</script>
I'm expecting the watch to trigger when product is returned, but it doesn't.
I could set the properties in the computed property like so:
computed: {
product() {
const payload = { collection: 'products', id: this.$route.params.productId }
const product = this.$store.getters.objectFromId(payload)
this.title = product.title
this.description = product.description
return product
}
}
But then the compiler gives me a warning: error: Unexpected side effect in "product" computed property
Accordingly to OP's comments, his intention is to get and load some initial data.
The common way to achieve this behavior is to place it inside created or mounted vuejs lifecycle hooks.
<template>
<div v-if="product" class="section">
<form>
<div class="control"><input type="text" class="input" v-model="title"></div>
<div class="control"><input type="text" class="input" v-model="description"></div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
title: '',
description: ''
}
},
created() {
this.getInitialData();
this.foo();
console.log("created!");
},
methods: {
getInitialData: function(){
const payload = {
collection: 'products',
id: this.$route.params.productId
};
var product = this.$store.getters.objectFromId(payload);
this.title = product.title;
this.description = product.description;
},
foo: function(){// ...}
},
}
</script>
Your structure is a bit all over the place. product is a computed, so it runs whenever it's source values change. (You have no control over when it runs.) It shouldn't have side effects (assignments this.description, this.title), or trigger network requests.
The code in product is fetching your source data. This belongs in methods, linked explicitly to a user action or a lifecyle event.
Why do you need to copy your data (this.description = product.description in watch:product)? Vue works best when you have your data (your app state) outside Vue, in a global variable say. Then your Vue components just transparently reflect whatever the app state is at a given moment.
Hope this helps.
Try the following:
watch: {
product: {
immediate: true,
handler(value) {
updateCode();
}
}
}

Categories

Resources