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
});
}
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
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 in a vue project, I'm very new to vue.
We have a db_handler.js in out /src/utility folder.
It looks like this:
import fakeApiCall from "./mock";
import axios from "axios";
import { DEBUG_MODE, API_ENDPOINT } from "./namespaces";
function fetchData(method, slug, payload) {
//axios.defaults.headers.withCredentials = true;
//return (!DEBUG_MODE) ? axios[method](`${API_ENDPOINT}${slug}`, payload) : fakeApiCall(slug);
return axios[method](`${API_ENDPOINT}${slug}`, payload);
/*var url = "http://localhost:8080" + slug
return axios({
method: method,
url: url,
headers: {
'Authorization': payload
}
});*/
}
function sendData(method, slug, payload) {
axios[method](`${API_ENDPOINT}${slug}`, payload);
}
export default fetchData
What I need to know:
How can I export my sendData()?
They used a short syntax so far because they only exported one function.
How can I export multiple functions? I also want the names to remain "fetchData" and "sendData"
EDIT:
I tried to apply the approaches of Iamhuynq and Bergi, but now something goes south. I am importing the functions first and foremost in
moduleUser.js and authUser.js which reside in /src/store/modules.
The authUser.js is used for the identification of the user, so of course it is used in the login screen. When I now try to login, I get "Type Error: Object undefined". I guess this is because the functions returning the server response are somehow failing or not found.
The codebase connected to this behavior is the Login screen, the db_handler which Ive already shown you and a module called "moduleAuth.js".
First, the login screen looks like this:
<template>
<div>
<h1>Login</h1>
<p>Name:</p>
<div class="inputbox">
<input ref="username" type='text' v-on:keydown.enter="userLogin">
</div>
<p>Password:</p>
<div class="inputbox">
<input class="inputbox" ref="password" type='password' v-on:keydown.enter="userLogin">
</div>
<p>{{error}}</p>
<button v-on:click='userLogin'>Login</button>
</div>
</template>
<script>
import store from "../store/store";
import { AUTH_REQUEST } from "../store/actions/auth";
export default {
data () {
return {
error: ""
}
},
methods: {
userLogin: function(){
this.error = '';
store.dispatch(AUTH_REQUEST,{username: this.$refs.username.value, password: this.$refs.password.value})
.then((res) => {
this.$router.push({path: '/profile'});
})
.catch((err) => {
this.error = err;
});
this.$refs.password.value = '';
}
}
}
</script>
<style>
.inputbox{
width: 25%;
}
</style>
moduleAuth.js, from which the AUTH_REQUEST vue-action is coming, looks like this:
import axios from "axios";
import Vue from 'vue';
import Vuex from 'vuex';
import {fetchData, sendData} from "../../utility/db_handler";
import { USER_REQUEST } from "../actions/user";
import { AUTH_REQUEST, AUTH_LOGOUT, AUTH_FAIL, AUTH_SUCCESS } from "../actions/auth";
import { METHOD_POST, JWT } from "../../utility/namespaces";
Vue.use(Vuex);
const storeAuth = {
state: {
token: localStorage.getItem(JWT) || '',
loginState: ''
},
getters: {
isAuthenticated: state => !!state.token,
getLoginState: state => state.loginState
},
mutations: {
[AUTH_REQUEST]: (state) => {
state.loginState = 'pending';
},
[AUTH_FAIL]: (state) => {
state.loginState = 'error';
},
[AUTH_SUCCESS]: (state, mToken) => {
state.loginState = '';
state.token = mToken;
},
[AUTH_LOGOUT]: (state) => {
return new Promise ((resolve, reject) =>{
state.loginState = '';
state.token = '';
localStorage.removeItem(JWT);
resolve();
//Catch?
})
}
},
actions: {
[AUTH_REQUEST]: ({ commit, dispatch }, uObj) => {
return new Promise((resolve, reject) => {
commit(AUTH_REQUEST);
fetchData(METHOD_POST, '/login',{
username: uObj.username,
password: uObj.password
}).then(function (res) {
commit(AUTH_SUCCESS,res.headers.authorization);
localStorage.setItem(JWT,res.headers.authorization);
axios.defaults.headers.common['Authorization'] = res.headers.authorization;
dispatch(USER_REQUEST);
resolve(res.data);
}).catch(function(err) {
commit(AUTH_FAIL);
reject(err);
})
})
},
[AUTH_LOGOUT]: ({ commit}) => {
commit(AUTH_LOGOUT);
}
}
}
export default storeAuth
Now, if just roll back the changes to the export/import sections, everything works. So the problem should definitely be connected to this.
you can use export
export function sendData() {...}
and you can import like this
import fetchData, { sendData } from '/src/utility/db_handler.js;'
Here my suggestion is, if you are exporting more then one function, you should use export method instead of export default. It will make your code more readable and ll use for future debugging.
export function function1(params) {
.......
}
export function function2() {
......
}
Here there is a two way to import functions
by using import { function1, function2} from "./exportedFunctionFile" make sure you are using same function name as you exported!
other method is use * as yourVariableName example import * as myFunctions from "./exportedFunctionFile" this would use when you are exporting too many functions now you can use your imported functions as myfunctions.function1()
if you want to export using default key word, export functions as object example export default {function1,function2} and you could use it like import * as myFunctions from "./exportedFunctionFile" which is similar as a second way of importion.
Hope it will Help you
export the functions in an object
export default {
sendData: sendData,
fetchData: fetchData
}
then to use
import * as DBHandler from '#/src/utility/db_handler'
...
DBHandler.sendData()
On the function files
func1(params) {
...
}
func2(params) {
...
}
export default default {
function1: function1,
function2: function2
}
On the other file
import * as _ from './module address'
then
_.default.func1.call(args)
_.default.func2.call(args)
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;
}
}
I'm trying to do a file upload with VueJS.
When a file is added to the input field it is buffered and saved in the vuex store.
I'm positive that the state updates, this shows in vue-devtool and I added a button to check it.
The DOM however is not re-rendering on the state change. I tried it both with the buffer array and just a regular string.
(when I click the commit button in vue-dev tools it updates the dom)
Please refer to this screenshot for a demonstration of the issue (this is after selecting a file and clicking the "console log state" button).
Demonstration
Component
<template>
<div id="home">
<h3>Upload Book (pdf)</h3>
<form v-on:submit="">
<input v-on:change="captureFile" type="file" placeholder="Select file..." />
<button type="submit">Upload</button>
<p>
<button v-on:click="consoleLog">Console log state</button>
{{filebuffer}}
</p>
</form>
</div>
</template>
<script>
export default {
name: 'home',
computed: {
filebuffer () {
return this.$store.state.fileBuffer
}
},
methods: {
captureFile (event) {
let file = event.target.files[0]
let reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {this.$store.dispatch('loadBuffer', reader)}
},
consoleLog () {
console.log(this.$store.state.fileBuffer)
}
}
}
</script>
Store
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
Vue.use(Vuex)
export const store = new Vuex.Store({
strict: true,
state,
mutations: {
saveBuffer (state, payload) {
state.fileBuffer = 'this will not update on the DOM'
console.log('saveBuffer committed', payload)
}
},
actions: {
loadBuffer ({commit}, payload) {
let buffer = Buffer.from(payload.result)
commit('saveBuffer', buffer)
}
}
})
you need to use Getters.
computed: {
filebuffer () {
return this.$store.getters.filebuffer;
}
}
and inside your store file
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex)
// State
const state = {
fileBuffer : '',
}
// Mutate the State
const mutations = {
saveBuffer (state, value) {
state.fileBuffer = value
}
// Access The State
const getters = {
fileBuffer : state => {
return state.fileBuffer
}
}
const actions = {
loadBuffer ({commit}, payload) {
let buffer = Buffer.from(payload.result)
commit('saveBuffer', buffer)
}
}
const module = {
state,
getters,
mutations,
actions
};
export default module;
Hope this help solve your problem .