I want to post a message to a backend route param with the current id.
How do I let the system know that I am passing this id?
Vuex action:
postMessage({commit}, payload, id) {
axios.post(`http://localhost:5000/channels/${id}/messages` ,payload)
.then((res) => {
commit(SET_POSTS, res.data)
})
}
This is posting the action but I need to pass the current channel id somehow. But the channel ID is in a different component?
postMessage() {
const postData = {
description: this.description,
timestamp: this.timestamp
};
this.$store.dispatch("postMessage", postData)
},
In a different component I have a channel list in my side menu, like discord for example and I display it like this
p.d-flex.align-items-center.channel-item(v-for="channel in channelName" :key="channel.id")
strong.hash.mr-3 #
| {{channel.channel_name}}
One of the primary benefits of Vuex is the ability to set state in one component and get it from another. In the other component, set some state like state.id. Then you can either pass that id to the action, or get it from state inside the action.
Here's an example of passing it:
Method
postMessage() {
const postData = {
id: this.$store.state.id,
description: this.description,
timestamp: this.timestamp
};
this.$store.dispatch("postMessage", postData)
}
Vuex actions always provide only 2 parameters, one for the context object (which contains commit, dispatch, etc.) and the payload. Change your action to:
Action
postMessage({ commit }, payload) {
axios.post(`http://localhost:5000/channels/${payload.id}/messages`, payload)
.then((res) => {
commit(SET_POSTS, res.data)
})
}
If you prefer, you can destructure the payload argument and use the spread operator to separate out the id from the rest:
Action
postMessage({ commit }, { id, ...payload }) {
axios.post(`http://localhost:5000/channels/${id}/messages`, payload)
.then((res) => {
commit(SET_POSTS, res.data)
})
}
You could also leave the id out of the payload and take it directly from state in the action:
Action
postMessage({ state, commit }, payload) {
axios.post(`http://localhost:5000/channels/${state.id}/messages`, payload)
.then((res) => {
commit(SET_POSTS, res.data)
})
}
Related
first of all i want to apologize for my title. I just dont know how to describe my problem.
I am trying to get a bad response from my server and when I try to display that my object is undefined
I have a base query methods here:
export const accountSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
login: builder.mutation({
query: credentials => ({
url: 'account/login',
method: 'POST',
body: { ...credentials },
})
}),
register: builder.mutation({
query: credentials => ({
url: 'account/register',
method: 'POST',
body: { ...credentials },
})
})
})
})
My handle submit on register page ->
const [register, { isLoading, isError }] = useRegisterMutation();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const result = await register({ name, nickName, email, password }).unwrap();
setRegisterResponse(result);
} catch (error) {
setRegisterResponse(error);
}
}
And my logic to show it. When i use console.log(registerResponse) it returnes two logs in console - first object is empty, second object with properties ->
{
isError &&
<h2>
Ooops.. something went wrong:
{
console.log(registerRespnse)
}
</h2>
}
Error in google console
You shouldn't need to call a setRegisterResponse state setter, because that response will just be available for you:
// see data and error here
const [register, { isLoading, isError, data, error }] = useRegisterMutation();
As why it logs undefined once: first the query finishes with an error (which will rerender the component and already fill error I showed above and set isError) and then the Promise resolves and your custom code sets your response local state, which causes a second rerender (and only on the second render, response is set)
I am trying to pass the following information to my dispatch function but the values are simply not begin recognized for some reason. They usually come out as undefined
My disptach call in my component:
methods: {
buscar(){
const formData = {
user: this.user,
password: this.password,
profile: this.profile
};
console.log('fomrdata: ',formData)
this.$store.dispatch('login', formData)
}
},
My method in store:
login(formData) {
console.log(formData)
},
All the values exist when i fill in my form and they show up in my component's console.log().
formData should be the second parameter in the action.
actions: {
login ({ commit }, formData) {
console.log(formData);
}
}
I'm trying to display a toast when a async request is finished.
I've implemented this process:
Single File Component calls updateUserProfile() actions in my VueX store
updateUserProfile() actions makes a outgoing HTTP request on a server using Axios
When succeeded, I use a mutation to update the user profile in my store and i would like to show a toast from my single file component.
Problem is that the response object is always undefined in my component. Where is my mistake ?
Error :
profile.vue?a62a:328 Uncaught (in promise) TypeError: Cannot read
property 'data' of undefined
at eval (profile.vue?a62a:328)
Store:
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data;
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
});
}
Component:
methods: {
// mix the getters into computed with object spread operator
...mapActions([
'updateUserProfile'
]),
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
}
this.updateUserProfile(userData).then( (response) => {
console.log('COMPONENT', response);
if (response.data.status === 200) {
toastr.success("Your profile has been successfully updated.");
}
});
}
}
Well,
It would be better idea if You trigger the toast from the Vuex store itself as mentioned below.
callAddToCart: ({ commit }, payload) => {
axiosBackend.put('/user/profile', userData, { headers: { Authorization:
state.authString }}).then(response => {
commit("setLoading", false, { root: true });
payload.cartKey = response.key;
commit("setNotification", {
type: 'success',
title: `title`,
});
commit("ADD_TO_CART", payload);
});
},
and inside mutation you can have a general notification toast and you can pass type, message and title as below.
setNotification(state, {type, message, title}) {
state.flash = {
type,
title,
message
}
}
NOTE: Do not forget to load toast element at the root level in order to display in the UI.
Here is working example
Hope this helps!
I'm fetching data from an external API via axios in the Vuex store, which gives me an array with objects - let's say - cities.
Each city has a zipCode, which I need to fetch city details with.
I want to add that details-object as a new key to the cities array.
Right now I'm fetching all cities and have another Vuex action to fetch the city details.
ACTION to fetch all cities
fetchCities: ({ commit, state, dispatch }) => {
let baseUrl = "https://my-url/cities";
let config = {
headers: {
accept: "application/json",
Authorization: ...
},
params: {
param1: "a",
param2: "b"
}
};
axios
.get(baseUrl, config)
.then(function(response) {
commit("UPDATE_CITIES_RAW", response.data.items);
})
.catch(function(error) {
console.log(error);
});
dispatch("fetchCityDetails");
},
MUTATION after fetching all cities
UPDATE_CITIES_RAW: (state, payload) => {
state.citiesRaw = payload;
},
Each object in that array has a zipCode, which I'm fetching details about this city with.
I tried to loop the citiesRaw array inside the action to fetch the details and commit a change for each iteration, but the array from the state is empty at this point, because the action gets called before the mutation.
ACTION to fetch city details
fetchCityDetails: ({ commit, state }) => {
let baseUrl = "https://my-url/cities/"
let config = {
headers: {
accept: "application/json",
Authorization: ...
}
};
// citiesRaw is empty at this point
state.citiesRaw.forEach(e => {
let url = baseUrl + e.zipCode;
axios
.get(url, config)
.then(function(response) {
commit("UPDATE_CITY_DETAILS", {
response: response.data,
zipCode: e.zipCode
});
})
.catch(function(error) {
console.log(error);
});
});
},
What are the best ways to wait for the first fetch and then update the array?
Should I even use the same array or create a new one to begin with?
Or is there even a better way to fetch based on fetched data in the Vuex store?
UPDATE
After fixing the dispatching before the async function even finished (thanks, #Y-Gherbi), I also refactored the way of fetching the details:
Component dispatches fetchCities
in fetchCities action: commit UPDATE_CITIES
in UPDATE_CITIES mutation: .map on the payload and create new object -> push all to state.cities
in fetchCities action: loop state.cities & dispatch fetchCityDetails(zipCode) for each city
in fetchCityDetails action: commit UPDATE_CITY_DETAILS
in UPDATE_CITY_DETAILS mutation: .map on state.cities and add cityDetails object to the referred city object
new actions
fetchCities: ({ commit, state, dispatch }) => {
let baseUrl = "https://my-url/cities";
let config = {
headers: {
accept: "application/json",
Authorization: ...
},
params: {
param1: "a",
param2: "b"
}
};
let url = baseUrl;
axios
.get(url, config)
.then(function(response) {
commit("UPDATE_CITIES", response.data.items);
state.cities.forEach(city => {
dispatch("fetchCityDetails", city.zipCode);
});
}
})
.catch(function(error) {
console.log(error);
});
},
fetchCityDetails: ({ commit }, zipCode) => {
let baseUrl = "https://my-url/cities";
let config = {
headers: {
accept: "application/json",
Authorization: ...
},
};
let url = baseUrl + "/" + zipCode;
axios
.get(url, config)
.then(function(response) {
commit("UPDATE_CITY_DETAILS", {
cityDetails: response.data,
zipCode: zipCode
});
})
.catch(function(error) {
console.log(error);
});
}
new mutations
UPDATE_CITIES: (state, cities) => {
// I don't need all data & I want to rename the keys from the response,
// so I create a new object
cities = cities.map(city => {
let obj = {};
obj.zipCode = city.zip_code
obj.key1 = city.key_1;
obj.key2 = city.key_2;
return obj;
});
state.cities.push(...cities);
},
UPDATE_CITY_DETAILS: (state, payload) => {
let cities = state.cities;
// add one details-object for each city
cities = cities.map(city => {
if (city.zipCode == payload.zipCode) {
city.cityDetails = payload.cityDetails;
}
return city;
});
state.cities = cities;
}
The question remains: Is there a better/more optimized approach to this kind of fetching?
I think that there are much better and elegant ways to deal with this but to simply help you with the bug (looping over an empty array that's expected to be an array of cities)
fix
Try moving the dispatch function to the next line after the commit function. The array (citiesRaw) is empty because you are calling the action before the data is fetched (because axios.get is an async operation)
alternative solution
As you said, the cities are displayed in a table with expandable rows, which are used to display the city details. Fetching 100-1000 cities is quite a lot of data but it's still one call to the back-end. But looping over all these items and doing a request for every one of them could be problematic from a performance and bandwidth (if that's the correct term for large data usage) perspective.
Personally, I would handle each request whenever the user actually needs this data. In your case whenever the user clicked on a row to expand it.
Storing it in the store?
Whenever you want to store the city details in the store is up to you. You could just keep it in the component but personally, I would use it as some kind of cache mechanism.
When the user clicked on a row, check if the details are already fetched
if(!state.cityDetails[key]) {
axios.get()
.then(res => {
// Commit a mutation that saves the fetched details
})
.catch((error) => {
// Handle error
})
} else {
// Use the 'cached' cityDetails
}
Your store could look something like this:
{
cityDetails: {
keyCouldBeIdOrZipcode: {...},
anOtherAlreadyFetchedCity: {...},
}
}
sorry for the typos and bad formatting. Typing code on a phone is horrible
Motivation
I store user credentials in redux store. They are filled when user logs in. I would like to have reusable method to fetch data with user's username and password.
State / auth
const initState = {
isLoading: false,
user: undefined,
auth_err: false
};
My attempt
const fetchData = props => async (url, method, body) => {
try {
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Base64.btoa(props.user.username + ":" + props.user.password)
},
body: body
});
console.log(response);
return response;
} catch (err) {
console.log(err);
}
};
const mapStateToProps = state => {
return {
user: state.auth.user
}
};
export const SENDREQUEST = connect(mapStateToProps)(fetchData);
Call
const response = await SENDREQUEST("http://localhost:8080/users", "GET");
But once I call it I get:
TypeError: Cannot call a class as a function
Is there any way at all to create such one?
Any help would be appreciated ♥
I am assuming that you know about redux and its middleware.
First of all the error comes from passing fetchData to the return value of connect : connect returns a function which is a HOC : takes a component, returns a component which is a class here that cannot be called as a function as you do.
A solution for your problem is to use mapDispatchToProps and a middleware, roughly as follow :
class LoginFormPresenter {
render () {
// render your login
return <form onSubmit={this.props.logUser}></form>
}
}
// This makes the LoginFormPresenter always receive a props `logUser`
const LoginFormConnector = connect((state => { user: state.user }), {
logUser: (e) => (
// create a credentials object by reading the form
const credentials = ...;
// return a valid redux action
return {
type: "LOGIN",
credentials
};
)
});
const LoginForm = LoginFormConnector(LoginFormPresenter);
// Create an ad hoc middleware
//
const middleware = ({ dispatch, getState }) => next => action => {
if (action.type === "LOGIN") {
// log your user
fetch()
.then(user => next({ type: "LOGIN", user }));
return next({ type: "PROCESSING_LOGIN" }); // indicate to redux that you are processing the request
}
// let all others actions pass through
return next(action);
};
So the mechanism works like this:
The LoginFormConnector will inject a props logUser into any component it is applied to. This props is a function wich dispatches an action with the credentials of your user. It will also inject a user props taken from the redux state for you to show your user.
Inside a redux middleware you catch the action and use your generic fetchData to process the request. When the request is resolved you dispatch the action to the reducer and update it. No data fetching occurs in the component itself, everything is handled at the middleware level.