I am trying to get a grip on Vuex by developing a small Games List app with a small NodeJS backend.
I have a Game List component with an option to update a game's completed status with a button
<template>
<div id="gameList">
<div v-for="game in allGames" :key="game._id" class="game">
<strong>{{ game.name }}</strong>
<em class="completed">{{ game.completed }}</em>
<button #click="updateCompleted(game._id)" v-if="game.completed === false">Set completed</button>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
name: "GameList",
methods: {
...mapActions(["getGames", "updateCompleted"])
},
computed: mapGetters(["allGames"]),
created() {
this.getGames();
}
};
</script>
And my Store
const state = {
gamelist: []
};
const getters = {
allGames: state => state.gamelist
};
const actions = {
getGames: async context => {
const response = await axios.get("http://localhost:8081/allgames");
context.commit("setGames", response.data);
},
updateCompleted: async (context, payload) => {
const response = await axios.put(
`http://localhost:8081/update/${payload}`, {
completed: true
}
);
context.commit('updateCompleted', response)
}
};
const mutations = {
setGames: (state, payload) => (state.gamelist = payload),
updateCompleted: state => console.log(state)
};
export default {
state,
getters,
actions,
mutations
};
The getter works perfectly and so does the action, but I can't seem to figure out how to mutate the state ( the gamelist ) after the PUT response so that I can update the view and display the game's new completed status without doing a refresh. The PUT response is just a "success" message, when the game in the database has been updated.
The GET response in the getGames action looks like this:
[{"completed":true,"_id":"5e0df1af63680526c07c670c","name":"Alien Isolation"},{"completed":false,"_id":"5e0df75ea252fe27e58577f6","name":"Red Dead Redemption"}]
Change these as follows:
context.commit('updateCompleted', {response, payload});
updateCompleted: (state, data) => {
if(data.response.status === 200){
let game = state.gamelist.findIndex(game => game._id === data.payload);
state.gamelist[game].completed = true;
}
}
Related
I have a React app in which a global state is setted by using redux, in one component a Form is filled and based on the inputs I do an axios request to the same endpoint from where redux fetch the data then I want to redirect to another component and filter the state based on this new request. The problem is that when I am redirected the same state that redux defined is shown and no the updated one.
My redux logic is this:
actionsCreator productActions.js
import { fetchProductsStart, fetchProductsSuccess, fetchProductsFailure } from '../slices/productsSlice';
import { baseUrl } from '../../shared/baseUrl';
export const fetchProducts = () => async dispatch => {
try {
dispatch(fetchProductsStart());
const response = await fetch(baseUrl+"products");
const data = await response.json();
dispatch(fetchProductsSuccess(data));
} catch (error) {
dispatch(fetchProductsFailure(error));
}
};
the slice reducer is productsSlice.js
import { createSlice } from "#reduxjs/toolkit";
const productsSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: false,
error: null
},
reducers: {
fetchProductsStart(state) {
state.loading = true;
state.error = null;
},
fetchProductsSuccess(state, action) {
state.products = action.payload;
state.loading = false;
},
fetchProductsFailure(state, action) {
state.error = action.payload;
state.loading = false;
}
}
});
export const { fetchProductsStart, fetchProductsSuccess, fetchProductsFailure } = productsSlice.actions;
export default productsSlice.reducer;
and my store configureStore.js
import { configureStore } from "#reduxjs/toolkit"
import productsReducer from "./slices/productsSlice"
export const store = configureStore({
reducer: {
products: productsReducer
}
})
the logic in the form that I mentioned above in the handleSubmit is:
handleSubmit(event){
event.preventDefault()
// redirect to the store component with the search criteria
// the search criteria will be passed as query parameters
var tipo = this.state.tipo
var marca = toTitleCase(this.state.marca)
var linea = this.state.linea
axios.get(baseUrl+'products' + '/?tipo=' + tipo + '&marca=' + marca + '&linea=' + linea )
.then((response) => {
console.log('response.data',response.data)
// here I am using the useNavigate hook to redirect and set the state
// with the response that is returned with the axios request
this.props.navigate("/store",{
state:{
products:response.data
}
});
})
.catch((error) => {
console.log(error)
})
}
How could I correctly update my state in order to filter this based on the inputs gotten from the form inputs? Is there a better way to do this I am kinda newbie with redux I can´t figure how to update the state.
EDIT: I forgot to mention that the component from where I am redirecting is a class component and I can´t change it to functional one
EDIT2: I just could change the logic so now the component from where I am redirecting is a functional Component
I'm getting the ID of a user based on the ID I pass in my route params. When I first load the page and access one of the users, the getter displays the ID from the route param accordingly, however once I go back, and click on another user, the ID from the param does not match with the getter. Instead the getter shows the ID of the previously accessed user. Can anyone kindly suggest a solution for this?
setup() {
const store = vuexStore;
const adminId = router.currentRoute.params.adminId;
console.log("ID param:", adminId);
getSelectedAdmin();
const selectedAdmin = computed(() => store.getters.getSelectedAdmin);
console.log("getter Id:", selectedAdmin.value.id);
function getSelectedAdmin() {
return store.dispatch(GET_ADMIN_BY_ID, adminId)
}
return {
selectedAdmin,
}
}
You should fetch the user in the onMounted hook.
(I use the script setup, vue-router#next & vuex#next)
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'
const store = useStore()
const route = useRoute()
const selectedAdmin = computed(() => store.getters['getSelectedAdmin'])
onMounted(async () => { await store.dispatch('GET_ADMIN_BY_ID', route.params.adminId) })
</script>
<template>
<div v-if="selectedAdmin">
{{ selectedAdmin.username }}
</div>
<div v-else>Loading ...</div>
</template>
In your store :
Set initial value for selectedAdmin to null
In the GET_ADMIN_BY_ID action, reset selectedAdmin to null before updating
const state = {
currentAdmin: null,
}
const mutations = {
SET_ADMIN: (state, payload) => state.currentAdmin = payload
}
const getters = {
getSelectedAdmin: (state) => state.currentAdmin
}
const actions = {
GET_ADMIN_BY_ID: ({ commit }, id) => {
commit('SET_ADMIN', null)
return axios.get(`admin/${id}`)
.then((response) => { commit('SET_ADMIN', response.data) })
.catch((err) => { commit('SET_ADMIN', null) })
}
Creating a Vuejs application whereby I use Vuex for state management between the components.In Vuex store, I have an action that fetches some data from an API (which works fine) then populate it to the state (via a mutation). Next, I pass the updated state to the component using getters.
Am having a problem populating data to the state (reactive manner) from the action (via mutation). In the DOM when I log the getter am getting an empty string.
Vuex Store
const getDefaultState = () => {
return {
clientDeposit: ''
}
}
//state
const state = getDefaultState();
//getters
const getters = {
getDeposit: (state) => state.clientDeposit
}
//actions
const actions = {
fetchClients({ commit}) {
const clientId ="23"
axios.post('/api/motor/fetchClients', {
ClientId: clientId,
})
.then((response)=> {
//console.log(response); //returns data
const deposit = response.data;
commit('setDeposit', deposit);
})
}
}
//mutations
const mutations = {
setDeposit: (state, value) => (state.clientDeposit = value)
}
export default {
state,
getters,
actions,
mutations
}
Component
<template>
<div>
<button onclick="fetchClients()">Fetch Clients</button>
Deposit (Via computed property) : {{ fetchDeposit }}
Deposit (from getter) : {{ getDeposit }}
</div>
</template>
<script>
import { mapGetters , mapActions } from "vuex";
import axios from "axios";
export default {
name: "",
data() {
return {
}
},
computed: {
...mapGetters([
"getDeposit"
]),
fetchDeposit(){
return this.getDeposit
},
},
methods:{
...mapActions([
"fetchClients"
])
}
};
</script>
<style scoped>
</style>
I'm working on a Laravel+Vue app. I'm using Vuex for state management. I'm trying to validate my form. Everything is going good but there's one issue i'm stuck in. The problem is when i try to submit the form first time the validationError state returns null (the default state not the updated one). When i submit the form again (to check validation), it logs the validationError object in the console. Any having idea why the validationErrors state is null on first submit.
NOTE: When i try to access validationErrors state inside template, it
works fine
store.js
import Vue from "vue";
import Vuex from "vuex";
import categories from "./modules/categories";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
categories
}
});
categories.js
import axios from "axios";
const state = {
categories: [],
validation_errors: null
};
const getters = {
allCategories: state => state.categories,
validationErrors: state => state.validation_errors
};
const actions = {
async fetchCategories({ commit }) {
const response = await axios.get("/api/categories");
commit("setCategories", response.data);
},
async addCategory({ commit }, { name, sku, unit, image, details }) {
try {
const formData = new FormData();
formData.append("name", name);
formData.append("sku", sku);
formData.append("unit", unit);
formData.append("image", image);
formData.append("details", details);
const res = await axios.post("/api/categories/add", formData);
commit("newCategory", res.data);
} catch (err) {
const errors = err.response.data.errors;
commit("formErrors", errors);
}
}
};
const mutations = {
setCategories: (state, categories) => (state.categories = categories),
newCategory: (state, category) => state.categories.unshift(category),
formErrors: (state, errors) => (state.validation_errors = errors)
};
export default {
state,
getters,
actions,
mutations
};
AddCategoryForm.vue
<template>
<form role="form" v-on:submit.prevent="handleSubmit">
<label for="name">Category Name</label>
<input
type="text"
class="form-control"
name="name"
id="name"
placeholder="Category Name"
v-model="category.name"
/>
<button type="submit" class="btn btn-primary">Add Category</button>
<!-- NOTE: I can access 'validationErrors' state here in the template -->
</form>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
data() {
return {
category: {
name: ""
}
};
},
computed: {
...mapGetters(["validationErrors"])
},
methods: {
...mapActions(["addCategory"]),
handleSubmit() {
this.addCategory(this.category);
console.log(this.validationErrors); // returns `null` on first submit
}
}
};
</script>
The action addCategory is async so that's why you should await it before checking this.validationErrors
async handleSubmit() {
await this.addCategory(this.category);
console.log(this.validationErrors); // returns `null` on first submit
}
OR
handleSubmit() {
this.addCategory(this.category),then(() => {
console.log(this.validationErrors); // returns `null` on first submit
});
}
Data is appending next to the datatable, not inside.
I am fetching data (array of records) from an API in actions of vuex and returning state (array) from getters to the components where datatables have been used.
import axios from "../../assets/constants";
import router from '../../router'
const state = {
users: []
}
const getters = {
users: state => state.users,
blockedUsers: state => state.blockedUsers,
user: state => state.user
}
const actions = {
async getUsers({ commit }) {
await axios.get(`user`)
.then(res => {
commit('setGetUsers', res.data)
})
.catch(err => console.log(err.response.data.message));
})
},
const mutations = {
setGetUsers: (state, newUsers) => (state.users = newUsers),
}
export default {
state,
getters,
actions,
mutations
}
<script>
import { mapGetters, mapActions } from "vuex";
export default {
methods: {
...mapActions(["getUsers"])
},
computed: mapGetters(["users"]),
created() {
this.getUsers();
$(".zero-configuration").DataTable();
}
};
</script>
Result should be as that data that I am fetching from API must show inside datatable.
As far I understand, issue that has been causing here is that
$(".zero-configuration").DataTable();
this is executing before
this.getUsers()
which shouldn't be correct explanation because I have used await with axios.
Can anyone explain why is this happening?
It turns out when I commit mutation after I get response from axios, it takes time to set the state. Since I am not using promise here, while the state is being mutate,
$(".zero-configuration").DataTable();
takes control from
this.getUsers()
and get executed before it finishes.
I encountered this problem by using promise in getUsers action
getUsers({ commit }) {
return new Promise(async (resolve) => {
await axios.get(`user`)
.then(async res => {
await commit('setGetUsers', res.data)
resolve()
})
.catch(err => console.log(err.response.data.message));
})
},
Now it works like a charm!