I have website build with VueJS on frontend and I want to hide each item that is deleted.
I have in store.js a property eventIsActive set to true:
export const store = new Vuex.Store({
state: {
eventIsActive: true
}
})
In a ShowItems.vue(grid version) I have a the delete method where I set the eventIsActive to false:
removeEvent() {
this.$http.delete('/event/' + item)
.then((response) => {
this.$store.state.eventIsActive = false;
this.$router.push('/events');
})
.catch((error) => {
alertify.error('Error', error);
})
}
On the same page I have the computed method for this property eventIsActive:
computed: {
getActiveEvent() {
return this.$store.state.eventIsActive;
}
}
On the HTML for this page I use the method from computed to hide the deleted component.
<template>
<div class="col-6 col-lg-4" v-if="getActiveEvent">
<p>{{itemTitle}}</p>
<p>{{itemSubtitle}}</p>
</div>
</template>
The problem is that when I delete an item, the rest of items are hidden, not only the one that is deleted.
Before I just used a simple eventIsActive: true in data() and set it to false in the removeEvent(). Was easier to use it, but because I have ShowItems.vue(list version), too, if I delete an Item in grid version, in the list version will still be there, until I refresh the page.
Can someone help me in this direction?
Vuex state is a global state that is shared by all components in the app.
So when you change eventIsActive all elements get the same state (true/false) and act accordingly.
Since showing/hiding an item is related to that specific item state, you need to have a local state for each item and change only it.
so in the data attribute of the component, add an active flag and use it instead:
data () {
....
active: true
}
....
removeEvent() {
this.$http.delete('/event/' + item)
.then((response) => {
this.active = false;
this.$router.push('/events');
})
.catch((error) => {
alertify.error('Error', error);
})
}
....
<template>
<div class="col-6 col-lg-4" v-if="active">
<p>{{itemTitle}}</p>
<p>{{itemSubtitle}}</p>
</div>
</template>
Related
So, I'm creating a Pokemon application and I would like to display the pokemon names using the api : https://pokeapi.co/api/v2/pokemon/.
I'm doing a fetch request on the api and then display the pokemon names in my template. I have 0 problem when I try to display only 1 pokemon but I have this error when I try to display all my pokemons using v-for.
Do you have any idea why I meet this error ?
<template>
<p class="dark:text-white"> {{pokemons[0].name}} </p> //working
<div v-for="(pokemon, index) in pokemons" :key="'poke'+index"> //not working...
{{ pokemon.name }}
</div>
</template>
<script>
const apiURL = "https://pokeapi.co/api/v2/pokemon/"
export default {
data(){
return{
nextURL:"",
pokemons: [],
};
},
created(){
this.fetchPokemons();
},
methods:{
fetchPokemons(){
fetch(apiURL)
.then( (resp) => {
if(resp.status === 200){
return resp.json();
}
})
.then( (data) => {
console.log(data.results)
// data.results.forEach(pokemon => {
// this.pokemons.push(pokemon)
// });
// this.nextURL = data.next;
this.pokemons = data.results;
console.log(this.pokemons);
})
.catch( (error) => {
console.log(error);
})
}
}
}
</script>
<style>
</style>
I've just pasted your code into a Code Pen and removed the working/not working comments and the code runs and shows the names.
Maybe the problem is in the parent component where this component is mounted, or the assignment of the :key attribute
try :key="'poke'+index.toString()", but I'm pretty sure js handels string integer concats quiet well.
Which version of vuejs do you use?
Edit from comments:
The parent component with the name PokemonListVue imported the posted component as PokemonListVue which resulted in a naming conflict. Renaming either one of those solves the issue.
In the error message posted, in line 3 it says at formatComponentName this is a good hint.
I am using Vuex and having trouble getting a user's data to be "reactive" after his profile has been updated. Here's my scenario:
My App.vue checks a user's properties during the created() hook
like so:
async created() {
await this.$store.dispatch('getSSOUser') // gets user from auth server
await this.$store.dispatch('fetchUserProfiles') // queries known user table to see if user has a profile
// set for global user state throughout app
await this.setUser()
// then loads the UI
this.isBusy = false
methods: {
setUser() {
const user = this.getUserProfileBySSOID(this.ssoUser.data.user_id)
this.$store.commit('SET_USER', user)
}
So now I have the user's profile (user object) to use throughout the app. Works good....but...when a user edits his profile in the app (for example, updates his phone number, etc) and clicks submit, I can't seem to get the state to refresh/see that there has been a change unless the user manually refreshes the page.
What is the recommended way to handle this issue? Do I need to run a dispatch to the user state on every route change? The user's profile is located at path: '/userEdit/:uid'
This is my app structure:
<div id="app">
<Banner />
<section class="container-fluid">
<loading-spinner v-if="isBusy"></loading-spinner>
<div v-else>
<AuthName class="text-right" />
<MainNav />
<main id="routerView">
<transition name="component-fade" mode="out-in">
<RouterView :key="$route.fullPath" />
</transition>
</main>
</div>
</section>
</div>
User profile update function:
ApiService.putUserProfile(this.user)
.then(() => {
this.loading = false
this.$router.push('/admin/users')
}
})
.catch(err => {
if (err.response) {
this.errors = err.response.data
} else {
if (err.request) {
this.errors = err.request
} else {
this.errors = err.message
}
}
this.loading = false
console.error('Error from update', err)
})
after you update the detail of the user you can fire an event which may will fetch the data from the server behind the scene.
methods:{
loadData(){
await this.$store.dispatch('getSSOUser') ;
await this.$store.dispatch('fetchUserProfiles');
await this.setUser()
this.isBusy = false
},
updateInfo() {
this.form.put('api/profile').then((response) => {
let userData = response.data.userdata;
Fire.$emit('ProfileEvent');
}
},
},
created(){
this.loadData();
Fire.$on('ProfileEvent', () => {
this.loadData();
});
}
may be firing an event after the there is any changes saved in the profile page and executing the function that is called when the component is created after the fired event may resolve your problem. Hope you will get the idea from above code example.
Using Vue TreeSelect Plugin to load a nested list of nodes from firebase backend. It's doc page says,
It's also possible to have root level options to be delayed loaded. If no options have been initially registered (options: null), vue-treeselect will attempt to load root options by calling loadOptions({ action, callback, instanceId }).
loadOptions (in my App.vue) dispatch vuex action_FolderNodesList, fetches (from firebase) formats (as required by vue-treeselect), and mutates the state folder_NodesList, then tries to update options this.options = this.get_FolderNodesList but this does not seems to work.
Here is the loadOptions method (in app.vue)
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
Vue errors out with Invalid prop: type check failed for prop "options". Expected Array, got String with value ""
I am not sure what am I doing wrong, why that does not work. A working Codesandbox demo
Source
App.vue
<template>
<div class="section">
<div class="columns">
<div class="column is-7">
<div class="field">
<Treeselect
:multiple="true"
:options="options"
:load-options="loadOptions"
:auto-load-root-options="false"
placeholder="Select your favourite(s)..."
v-model="value" />
<pre>{{ get_FolderNodesList }}</pre>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Treeselect from "#riophae/vue-treeselect";
import "#riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
data() {
return {
value: null,
options: null,
called: false
};
},
components: {
Treeselect
},
computed: mapGetters(["get_FolderNodesList"]),
methods: {
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
}
};
</script>
Store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
folder_NodesList: ""
},
getters: {
get_FolderNodesList(state) {
return state.folder_NodesList;
}
},
mutations: {
mutate_FolderNodesList(state, payload) {
state.folder_NodesList = payload;
}
},
actions: {
action_FolderNodesList({ commit }) {
fmRef.once("value", snap => {
var testObj = snap.val();
var result = Object.keys(testObj).reduce((acc, cur) => {
acc.push({
id: cur,
label: cur,
children: recurseList(testObj[cur])
});
return acc;
}, []);
commit("mutate_FolderNodesList", result);
});
}
}
});
Any help is appreciated.
Thanks
It seems you are calling this.options which would update the entire element while only the current expanding option should be updated.
It seems loadOptions() is called with some arguments that you can use to update only the current childnode. The first argument seems to contain all the required assets so I wrote my loadTreeOptions function like this:
loadTreeOptions(node) {
// On initial load, I set the 'children' to NULL for nodes to contain children
// but inserted an 'action' string with an URL to retrieve the children
axios.get(node.parentNode.action).then(response => {
// Update current node's children
node.parentNode.children = response.data.children;
// notify tree to update structure
node.callback();
}).catch(
errors => this.onFail(errors.response.data)
);
},
Then I set :load-options="loadTreeOptions" on the <vue-treeselect> element on the page. Maybe you were only missing the callback() call which updates the structure. My installation seems simpler than yours but it works properly now.
When I update the parent's singleIssue variable, it does not get updated inside my <issue> component. I am passing it there using props. I have achieved this in other projects already, but I can't seem to get what I am doing wrong.
I have reduced my code to the relevant parts, so it is easier to understand.
IssueIndex.vue:
<template>
<div class="issue-overview">
<issue v-if="singleIssue" :issue="singleIssue"></issue>
<v-server-table url="api/v1/issues" :columns="columns" :options="options" ref="issuesTable">
<span slot="name" slot-scope="props">{{props.row.name}}</span>
<div slot="options" slot-scope="props" class="btn-group" role="group" aria-label="Order Controls">
<b-btn class="btn-success" v-b-modal.issueModal v-
on:click="showIssue(props.row)">Show</b-btn>
</div>
</v-server-table>
</div>
</template>
<script>
export default {
mounted() {
let app = this;
axios.get('api/v1/issues/')
.then(response => {
app.issues = response.data;
})
.catch(e => {
app.errors.push(e);
});
},
data: () => {
return {
issues: [],
singleIssue: undefined,
columns: ['name', 'creation_date', 'options'],
options: {
filterByColumn: true,
filterable: ['name', 'creation_date'],
sortable: ['name', 'creation_date'],
dateColumns: ['creation_date'],
toMomentFormat: 'YYYY-MM-DD HH:mm:ss',
orderBy: {
column: 'name',
ascending: true
},
initFilters: {
active: true,
}
}
}
},
methods: {
showIssue(issue) {
let app = this;
app.singleIssue = issue;
// This gets the action history of the card
axios.get('api/v1/issues/getCardAction/' + issue.id)
.then(response => {
app.singleIssue.actions = response.data;
})
.catch(error => {
// alert
});
}
}
}
</script>
Issue.vue:
<template>
<div>
{{ issue }}
</div>
</template>
<script>
export default {
props: ['issue']
}
</script>
So after showIssue() is triggered, it will get actions for the issue. But after then, I can't see the actions in the issue component.
If I update the issue-model in the issue component using form inputs, it will also start showing the actions. So I assume it's just in a weird state where it needs a refresh.
Thanks in advance!
If the singleIssue.actions property does not exist at the time when you're setting it, Vue will not be able to detect it. You need to use $set, or just define the property before you assign singleIssue to app.
Change this:
app.singleIssue = issue;
to this:
issue.actions = undefined;
app.singleIssue = issue;
The app.singleIssue property is reactive (because it was declared in the data section), so Vue will detect when this property is assigned to and make the new value reactive if it isn't already. At the time when issue is being assigned, it will be made reactive without the actions property, and Vue cannot detect when new properties are being added to reactive objects later on (hence why $set is required for those situations).
I'm trying to hide the main app navigation bar based on if the route is on a given path.
In my App.vue component, in the created() method. I do check to see if the route is x || y, if either of those are true, I set my Vuex state of show to false. If it is any other route besides those two, I set show = true.
Then in my template I do this
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
I'm noticing in Vuex tools that my mutations aren't even registering so I'm not sure why that is. Do they need to be actions instead? Here is my full code.
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
<script>
import Navigation from './components/Navigation/Navigation'
import { firebaseAuth } from './firebase/constants'
import store from './store/index'
export default {
name: 'app',
components: {
Navigation
},
computed: {
show () {
return store.state.navigation.show
}
},
created() {
// Checks for a user and dispatches an action changing isAuthed state to true.
firebaseAuth.onAuthStateChanged(user => {
console.log(store.state.authentication);
console.log(user);
store.dispatch('checkUser', user);
});
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/dashboard/products" || this.$route.path === "/dashboard/settings") {
store.commit('hideNav');
} else if (this.$route.path !== "/dashboard/products" || this.$route.path !== "/dashboard/settings") {
store.commit('showNav');
}
}
};
</script>
This may not be working as created is called only once after the instance is created. but when routes changes, it will not be called, so not triggering the mutations you are expecting to trigger on route change, instead of this, you can put a watch on route, so on each route change, you can check whether to show your Nav Bar or not, like following;
Working fiddle: http://jsfiddle.net/ofr8d85p/
watch: {
$route: function() {
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/user/foo/posts") {
store.commit('SHOWNAV');
} else {
store.commit('HIDENAV');
}
}
},