How do getters, mutations and actions play together in Vuex? - javascript

I'm curious about Vuex for my personal project and more specifically how the different properties play together. I apologize in advance if this is a repetitive question.
I'm using Vue3 and this is the way I've configured the store:
// store/index.js
import { createStore } from "vuex";
import axios from "axios";
let connected = false;
axios.defaults.withCredentials = true;
axios.defaults.baseURL = import.meta.env.VITE_BASE_URL;
export default createStore({
state: {
connected,
},
getters: {
getConnected: (state) => {
return state.connected;
},
},
mutations: {
setConnected(state, isConnected) {
console.log("in mutations");
return (state.connected = isConnected);
},
},
actions: {
isConnected: ({ commit }) => {
axios
.get("/auth/me")
.then(() => {
console.log("here positive");
commit("setConnected", true);
})
.catch(() => {
console.log("here negative");
commit("setConnected", false);
});
},
},
modules: {},
});
The state is what we're storing, the mutations are the operations on the state and the actions are the operations on the mutations via commits.
Here's my Vue page:
<template>
<v-row justify="start">
<nav>
<router-link to="/">Home</router-link> |
<router-link :to="connected ? '/me' : '/signup'">{{
connected ? "Profile Page" : "Sign up"
}}</router-link>
|
<router-link :to="connected ? '/logout' : '/login'">{{
connected ? "Logout" : "Login"
}}</router-link>
</nav>
</v-row>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
name: "NavBar",
data() {
return {
connected: false,
};
},
methods: {
...mapGetters(["getConnected"]),
...mapActions(["isConnected"]),
},
mounted() {
this.connected = this.getConnected();
console.log("connected", this.connected);
},
};
</script>
<style>
nav {
padding: 30px;
}
nav a {
font-weight: bold;
color: #2c3e50;
}
nav a.router-link-exact-active {
color: #42b983;
}
</style>
My question, how do I trigger the actions? Do I need to explicitly call the method via mapActions or am I missing something?
TIA!

You can call it like any other method:
async mounted() {
await this.isConnected()
this.connected = this.getConnected();
console.log("connected", this.connected);
},
more here

Related

Vuex state doesn't change after login / logout

I want to show different button in Header component, based on user authentication
Header.vue
<template>
<div class="utf_right_side">
<div class="header_widget">
<router-link :to="{name:'login'}" class="button border sign-in popup-with-zoom-anim" v-if="!isAuth"><i class="fa fa-sign-in"></i>Login</router-link>
<a class="button border sign-in popup-with-zoom-anim" v-if="isAuth" href="" #click.prevent="logout" :key="componentKey"><i class="fa fa-sign-in"></i>Logout</a>
<i class="sl sl-icon-user"></i> Add Listing</div>
</div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
name:"default-layout",
data(){
return {
user:this.$store.state.auth.user,
isAuth: this.$store.state.auth.authenticated,
}
},
methods:{
...mapActions({
signOut:"auth/logout"
}),
async logout() {
await axios.post('/logout').then(({data})=>{
this.signOut();
this.$parent.forceRerender();
})
},
},
}
</script>
As you can see based on the variable isAuth which comes from the vuex state I want to show different buttons, but after logging in state doesn't change and it still show the old button (before authentication). If I refresh the page manually (f5) it shows the correct button.
Login.vue:
<script>
import { mapActions } from 'vuex'
export default {
name:"login",
data(){
return {
auth:{
email:"",
password:""
},
validationErrors:{},
processing:false
}
},
methods:{
...mapActions({
signIn:'auth/login'
}),
async login(){
this.processing = true
await axios.get('/sanctum/csrf-cookie')
await axios.post('/login',this.auth).then(({data})=>{
this.signIn()
}).catch(({response})=>{
if(response.status===422){
this.validationErrors = response.data.errors
}else{
this.validationErrors = {}
alert(response.data.message)
}
}).finally(()=>{
this.processing = false
})
},
}
}
</script>
vuex auth.js which is included in index.js vuex file:
import axios from 'axios'
import router from '#/router'
export default {
namespaced: true,
state:{
authenticated:false,
user:{}
},
getters:{
authenticated(state){
return state.authenticated
},
user(state){
return state.user
}
},
mutations:{
SET_AUTHENTICATED (state, value) {
state.authenticated = value
},
SET_USER (state, value) {
state.user = value
}
},
actions:{
login({commit}){
return axios.get('/api/user').then(({data})=>{
commit('SET_USER',data)
commit('SET_AUTHENTICATED',true)
router.push({name:'home'})
}).catch(({response:{data}})=>{
commit('SET_USER',{})
commit('SET_AUTHENTICATED',false)
})
},
logout({commit}){
commit('SET_USER',{})
commit('SET_AUTHENTICATED',false)
}
}
}
So when user logs in it enters login method in auth.js and set the correct state, here:
commit('SET_USER',data)
commit('SET_AUTHENTICATED',true)|
After that it redirects to route with name home, but Header still show old button and when I refresh the page, the correct button is displayed.
Instead of store state isAuth: this.$store.state.auth.authenticated try to import getters
import { mapGetters } from 'vuex'
and use getter in computed property, which is reactive, like:
computed: {
...mapGetters({ isAuth: 'auth/authenticated' }),
},

Why is this cardDetails component which receives an id through a Vuex action is not rendering?

I'm quite new with Vue and Vuex, actually I'm just doing a little app which display events, and you can access to each card event and see details through the card's ID. I passed all the code to vuex Store, and I'm having problems rendering individual cards, based on the error I understand that the problem is accessing the ID, but I'm console logging the props.id, and you can see the result in console:123 (I clicked on the first card and that's the correct ID)
So here's the EventList Component:
once I click on one of 'em, I get this console error:
Here is the code:
EventDetails component:
<template>
<div class="event-card">
<h2>You are on {{ $route.params.props.id }}</h2>
<span>#{{ event.time }} on {{ event.date }}</span>
<h4>{{ event.title }}</h4>
<p>{{ event.description }}</p>
</div>
</template>
<script>
import store from "#/store";
import { computed } from "#vue/reactivity";
import { onBeforeMount, onMounted, reactive, ref, toRefs } from "vue";
import { useStore } from "vuex";
export default {
name: "EventDetails",
props: ["id", "modelValue"],
setup(props) {
const state = reactive({
events: [],
event: {},
});
const message = ref("AsapRocky");
console.log(props.id)
onMounted(() => {
store.dispatch('fetchEvent', props.id)
});
const event = computed(() => {
return store.state.event;
});
return {
event,
message,
...toRefs(state),
};
},
};
</script>
store code:
import { createStore } from 'vuex'
import apiClient from '../services/EventService';
export default createStore({
state: {
user: 'TommyDemian',
events: [],
event: {}
},
mutations: {
SET_EVENTS(state, events){
state.events = events;
},
SET_EVENT(state, event) {
state.event = event;
}
},
actions: {
fetchEvents({ commit }){
apiClient
.getEvents()
.then((response) => {
commit("SET_EVENTS", response.data)
})
.catch((error) => {
console.log(error);
});
},
fetchEvent({ commit }, id){
apiClient.getEvent(id)
.then((response) => {
commit("SET_EVENT", response.data)
})
.catch((error) => {
console.log(error);
});
}
},
getters: {
},
modules: {
}
})
The stacktrace indicates the problematic reference to id is actually in {{ $route.params.props.id }} from your template.
I assume you were trying to access the component's id prop, which would not be in the route parameters:
<!-- <h2>You are on {{ $route.params.props.id }}</h2> --> ❌
<h2>You are on {{ id }}</h2> ✅

Vue.js Typescript I get the data using getter but can't reach it in methods

I am new to Typescript with vuex. I simply want to fetch user list from the backend. Put in the store. I declared custom user type
export interface User {
id: number;
firstName: string;
lastName: string;
email: string;
}
in my vuex.d.ts file, I declare store module like:
import { Store } from "vuex";
import { User } from "./customTypes/user";
declare module "#vue/runtime-core" {
interface State {
loading: boolean;
users: Array<User>;
}
interface ComponentCustomProperties {
$store: Store<State>;
}
}
in my store I fetch the users successfully and commit the state:
import { createStore } from "vuex";
import axios from "axios";
import { User, Response } from "./customTypes/user";
export default createStore({
state: {
users: [] as User[], // Type Assertion
loading: false,
},
mutations: {
SET_LOADING(state, status) {
state.loading = status;
},
SET_USERS(state, users) {
state.users = users;
},
},
actions: {
async fetchUsers({ commit }) {
commit("SET_LOADING", true);
const users: Response = await axios.get(
"http://localhost:8000/api/get-friends"
);
commit("SET_LOADING", false);
commit("SET_USERS", users.data);
},
},
getters: {
userList: (state) => {
return state.users;
},
loadingStatus: (state) => {
return state.loading;
},
},
});
I set the getters, I sense that I don't need to set getter for just returning state however this is the only way I could reach the data in my component. Please advise if there is a better way to do it. In my component I accessed the data like:
<div class="friends">
<h1 class="header">Friends</h1>
<loading v-if="loadingStatus" />
<div v-else>
<user-card v-for="user in userList" :user="user" :key="user.id" />
<pagination />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { mapGetters } from "vuex";
import { User } from "../store/customTypes/user";
=import UserCard from "../components/UserCard.vue";
import Loading from "../components/Loading.vue";
import Pagination from "../components/Pagination.vue";
export default defineComponent({
name: "Friends",
components: {
UserCard,
Loading,
Pagination,
},
static: {
visibleUsersPerPageCount: 10,
},
data() {
return {
users: [] as User[],
currentPage: 1,
pageCount: 0,
};
},
computed: {
...mapGetters(["loadingStatus", "userList"]),
},
mounted() {
this.$store.dispatch("fetchUsers");
this.paginate()
},
methods: {
paginate () {
// this.users = this.$store.state.users
console.log(this.$store.state.users)
console.log(this.userList)
}
}
});
</script>
Now when I get userList with getters, I successfully get the data and display in the template. However When I want to use it in the method, I can't access it when component is mounted. I need to paginate it in the methods. So I guess I need to wait until promise is resolved however I couldn't figure out how. I tried
this.$store.dispatch("fetchUsers").then((res) => console.log(res)) didn't work.
What I am doing wrong here?
An action is supposed to return a promise of undefined, it's incorrectly to use it like this.$store.dispatch("fetchUsers").then(res => ...).
The store needs to be accessed after dispatching an action:
this.$store.dispatch("fetchUsers").then(() => {
this.paginate();
});

When calling the EventBusses in vueJS, one of them is not working at all

So I have added a second bus to my code that runs on create, but no matter in which order I call the Busses the second bus (eventBus2) is never called and then returns no data. By printing some console logs I get the feeling that that eventBus2.$on is never executed. Is there some Vue rule that I'm not aware of, any suggestions?
Item.vue
<template>
<div>
<table>
<tr
v-for="item in info"
:key="item.id"
#click="editThisItem(item.id)"
>
<td>{{ item.name}}</td>
<td>{{ item.number}}</td>
<td>{{ item.size}}</td>
</tr>
</table>
</div>
</template>
<script>
import Something from "./Something.vue";
import axios from "axios";
import { eventBus } from "../main";
import { eventBus2 } from "../main";
export default {
components: { Something },
name: "Item",
data() {
return {
selected_item_id: 0,
info: null,
};
},
methods: {
editThisItem(bolt) {
this.selected_item_id = bolt;
eventBus2.$emit("itemWasSelected", this.selected_item_id);
eventBus.$emit("newTabWasAdded", "edit-item");
},
},
mounted() {
axios
.get("http://localhost:8080/items")
.then((response) => (this.info = response.data._embedded.artikli));
},
};
</script>
EditItem.vue
<script>
import Something from "./Something.vue";
import axios from "axios";
import { eventBus2 } from "../main";
export default {
components: { Something},
name: "Edit-item",
data() {
return {
info: null,
select_number: 0,
select_name: "",
selected_item_id: -1,
priv_item: {
id: 0,
size: "big"
},
};
},
mounted() {
if (this.selected_item_id != -1) {
axios
.get("http://localhost:8080/items/" + this.selected_item_id)
.then((response) => (this.priv_item = response.data));
}
},
created() {
eventBus2.$on("itemWasSelected", (data) => {
this.selected_item_id = data;
console.log(" + " + data);
//this console log does not even print the "+", the data is empty
});
console.log(this.selected_item_id);
},
};
</script>
main.js
export const eventBus = new Vue();
export const eventBus2 = new Vue();
you're expecting itemWasSelected and emitting WasSelected they should be the same.
PD: that can be done in one line.
import { eventBus } from "../main";
import { eventBus2 } from "../main";
import { eventBus, eventBus2 } from "../main";

Gender information disappears when the page is refreshed

Variable named jso disappears when the page is refreshed. Also, is there any other way to send store information than method?
It will work when the page is refreshed and reopened without using a button.
view/userProfile.vue
<template>
<div>
<v-list>
<v-list-item>
{{userdata['username']}}</v-list-item>
<v-list-item> {{userdata['id']}}</v-list-item>
<v-list-item> {{userdata['email']}}</v-list-item>
<v-list-item> {{userdata['phone_number']}}</v-list-item>
<v-list-item> {{userdata['first_name']}} {{userdata['last_name']}}</v-list-item>
<v-list-item > {{userdata['gender']}}</v-list-item>
{{userdata['educational_status']}}
</v-list>
<hr>
{{this.profileData.gender}}
{{jso}} --> variable that disappears on page refresh
</div>
</template>
<script>
Only jso disappears on refresh page:
import Vuetify from "vuetify"
import {UserData} from "../../store/userModule";
import {JsonChoiceData} from "../../store/choiceStore";
import jsonDict from "../../jsonFiles/data.json"
import JsonFile from "../../jsonFiles/jsonfile"
export default {
name: "userProfile",
data(){
return {
profileData:{
username:'',
first_name:'',
last_name: '',
email:'',
phone_number:'',
birthday:'',
gender:'',
educational_status:'',
martial_status:'',
},
}
},
created(){
this.$store.dispatch('initUserData')
this.$store.dispatch('inijson')
},
computed:{
jso(){
return this.$store.getters.user
},
userdata (){
for(var i in this.$store.getters.getUser){
return this.$store.getters.getUser[i]
}
return this.$store.getters.getUser},
},
methods:{
getjsondata(){
console.log(this.userdata['gender'] + "methods")
this.$store.dispatch('getJsonData',this.userdata['gender'])
console.log(this.userdata['gender'])
}
},
mounted(){
this.getjsondata()
}
}
</script>
<style scoped>
</style>
store
import JsonFiles from '../jsonFiles/jsonfile'
import Jsondict from '../jsonFiles/data.json'
import jsonfile from "../jsonFiles/jsonfile";
export const JsonChoiceData = {
state: {
user: [],
},
getters: {
user(state) {
return state.user
},
},
mutations: {
inijson(state, user) {
state.user = user
},
getsonData: function (state, userinput) {
var getJsoncleandata = jsonfile.JsonData(userinput, Jsondict.Gender)
state.user = getJsoncleandata
return getJsoncleandata
}
},
actions: {
inijson(context){
context.commit('inijson', this.getsonData)
},
getJsonData(context,userinput){
context.commit('getsonData',userinput)
}
}
}
getsonData is a mutation and shouldn't be used as payload of another mutation. You are also trying to dispatch initUserData action which is not inside your store. I think that you can try to commit getsonData mutation inside your inijson action.
mutations: {
...,
getsonData: function(state, userinput) {
const getJsoncleandata = jsonfile.JsonData(userinput, Jsondict.Gender);
state.user = getJsoncleandata;
}
...
},
actions: {
inijson(context) {
context.commit('getsonData', null)
},
...
}
Then inside created hook of your component dispach only inijson action:
...
created() {
this.$store.dispatch('inijson')
},
...
If you see strange date make sure that jsonfile.JsonData(userinput, Jsondict.Gender) doesn't return a Promise.
Instead of using global $store you can also consider to use vuex store mappers. component binding helpers

Categories