Vuex: Fetch data with axios based on already fetched data - javascript

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

Related

my error object is undefined when i`m using rtk query with try/catch

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)

React Native Screen wont re-render after fetching data and setting state using HTTP request

I am using the react-native google API to request calendar data via HTTP request using axios.
After the user clicks a login button the function calendarData is initiated and successfully pulls the data and I use setCalendarEvents to set the state of my page to this response data.
I expected this to re-render the screen and display the data but it is not...How can I initiate a page refresh after this data is received from the HTTP request without a manual re-render?
STATE
const [calendarEvents, setCalendarEvents] = useState([]);
calendarData function RUNS AFTER LOG IN BUTTON IS PRESSED BY USER
const calendarData = async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: `['CLIENT ID]`,
iosClientId: `['CLIENT ID']`,
scopes: [
"profile",
"email",
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.events",
],
});
if (result.type === "success") {
axios({
//HTTP GET REQUEST FOR DATA
method: "get",
baseURL: "https://www.googleapis.com/calendar/v3/calendars/['USER CALENDAR]/events?key=['API KEY']",
headers: {
Authorization: "Bearer " + result.accessToken,
Accept: "application/json",
},
})
.then((response) => {
const responseDataArray = [];
//RESPONSE DATA
response.data["items"].map((event) => {
if (typeof event["start"].dateTime !== undefined) {
responseDataArray.push(event);
}
//SET STATE TO RETREIVED AND FILTERED DATA STORED IN responseDataArray
setCalendarEvents(responseDataArray);
});
})
//CATCH ERRORS
.catch((error) => console.log(error));
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
};
WHERE DATA SHOULD BE RENDERED ON THE SCREEN AFTER SUCCESSFUL GET
return (
<View>
{calendarEvents.map((event) => {
<View>{event}</View>
}
}
</View>
)
EXAMPLE OF RESPONSE DATA ITEM
I am looking to filter out "start":{"dateTime":"2021-04-16T17:30:00-04:00"} if it exists
{"kind":"calendar#event","etag":"\"3237003518996000\"","id":"7t1q67ai1p7t586peevd7s9mhg","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=N3QxcTY3YWkxcDd0NTg2cGVldmQ3czltaGcgbWF0dEBoZWFydGhkaXNwbGF5LmNvbQ","created":"2021-04-14T16:45:34.000Z","updated":"2021-04-15T15:49:19.498Z","summary":"customer journey beta buddies","creator":{"email":"meilin#hearthdisplay.com"},"organizer":{"email":"meilin#hearthdisplay.com"},"start":{"dateTime":"2021-04-16T17:30:00-04:00"},"end":{"dateTime":"2021-04-16T18:30:00-04:00"},"iCalUID":"7t1q67ai1p7t586peevd7s9mhg#google.com","sequence":0,"attendees":[{"email":"meilin#hearthdisplay.com","organizer":true,"responseStatus":"accepted"},{"email":"matt#hearthdisplay.com","self":true,"optional":true,"responseStatus":"accepted"},{"email":"susie#hearthdisplay.com","responseStatus":"accepted"},{"email":"nathalie#hearthdisplay.com","responseStatus":"accepted"}],"hangoutLink":"https://meet.google.com/xyb-qhpb-uej","conferenceData":{"entryPoints":[{"entryPointType":"video","uri":"https://meet.google.com/xyb-qhpb-uej","label":"meet.google.com/xyb-qhpb-uej"},{"entryPointType":"more","uri":"https://tel.meet/xyb-qhpb-uej?pin=3822838393771","pin":"3822838393771"},{"regionCode":"US","entryPointType":"phone","uri":"tel:+1-818-514-5197","label":"+1 818-514-5197","pin":"222000933"}],"conferenceSolution":{"key":{"type":"hangoutsMeet"},"name":"Google Meet","iconUri":"https://fonts.gstatic.com/s/i/productlogos/meet_2020q4/v6/web-512dp/logo_meet_2020q4_color_2x_web_512dp.png"},"conferenceId":"xyb-qhpb-uej","signature":"AGirE/Jmi4pFHkq0kcqgRyOuAR2r"},"reminders":{"useDefault":true},"eventType":"default"}
Maybe try with this, add conditional rendering :
return ({
calendarEvents.length>0 &&
calendarEvents?.map(...your code)
})

How to make addition of state in react native

I want to addition of two state,
the both value of state are extracted from json object using a fetch request
constructor(props) {
super(props);
this.state = {
charge:0,
total:0,
};
}
dataFatch()
{
const tot = this.state.total;
fetch('https://xyz.in/app/reg.php', {
method: 'POST',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
'item_id':ids,
'func':'item_data',
})
}).then((response) => response.json())
.then((json) => {
this.setState({'total':json.total});
this.setState({'charge':json.charge});
}).catch((err) => { console.log(err); });
}
but after a fetching data charge=30 and total=80
i preform addition like
Let add = this.state.charge+this.state.total;
console.log(add);
but the result is 3080 instead of 110
so how can i solve this problem
It looks like an you are doing concat strings instead of number
I don't see add logic in your api request function but as per you showed you can fix it with below
let add = Number(this.state.charge) + Number(this.state.total);
console.log(add);
But important is you understand that the state you stored isn't number but typeof string.
So while updating state you can make sure it's number not an string so you don't need to convert it into number while doing concat.

How to pass data with specific ID to backend route param?

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)
})
}

Is there a difference in data/promise returned from axios get and post?

I'm working on a React application that makes use of an imported object with a get request to an api and a post request to a related API.
When creating a new instance of my service in the frontend in React, I am able to successfully use the '.then' & '.catch' functions to access the returned data ONLY from the get request.
When using the post request from the same object, when trying to access the response object, I get a (paraphrased) '.then' is not a function on undefined.
Only when I explicitly write out the post request in my form submit function (without consuming a service) and handling the object there am I able to check the response and subsequently set the state.
What is the appropriate/best practice way for using axios in React and why am I not able to access the response object when I create a new instance of a service?? Much appreciated!
Service:
import axios from 'axios';
class ProductServices {
getAllProducts(){
return axios.get('https://somecustomAPIURL')
}
postProduct(somePathConfig){
axios.request({
url: 'https://somecustomAPIURL' + somePathConfig,
method: 'post',
headers: {'some-custom-header': process.env.REACT_APP_API_POST_KEY}
})
}
}
export default ProductServices;
React Code instantiating and consuming the service (note, that getAllProducts works just fine, but trying to consume a response object in postProduct returns an '.then' is undefined)
constructor(){
super();
this.state = {
products: [],
productID: null,
showModal: false
}
this.ProductServices = new ProductServices();
}
getAllProducts = () => {
this.ProductServices.getAllProducts()
.then((response) => {
let items = response.data.data.items;
this.setState({
products: items,
productID: items[0].id
});
return response;
})
.catch((error) => {
console.log('Error!', error);
return error;
})
}
handleFormSubmit = (e) => {
e.preventDefault();
let productID = this.state.productID;
this.ProductServices.postProduct(productID)
.then((response) => {
this.setState({showModal: true}, () => console.log('Success!'));
return response;
})
.catch((err) => {
console.log('Error!', err);
})
}
You missed return before axios.request.
import axios from 'axios';
class ProductServices {
...
postProduct(somePathConfig){
return axios.request({
url: 'https://somecustomAPIURL' + somePathConfig,
method: 'post',
headers: {'some-custom-header': process.env.REACT_APP_API_POST_KEY}
})
}
...
Also, instead of axios.request, you can use axios.post like axios.get
return axios.post(url, body, { headers });
return axios.get(url, { headers });
return axios.put(url, body, { headers });
return axios.delete(url, { headers });
return axios.request(axiosConfigOptions);

Categories

Resources