promise keep return the empty array - javascript

constructor() {
super();
this.state = {
detailWallet: [],
user: [],
historia: [],
namaPertama: [],
namaKirim: [],
}
componentDidMount() {
this.getDetailAPI();
this.getDetailPlaceAPI();
this.getHistoria();
}
getNameAPI = () => {
const id = this.props.match.params.id;
return new Promise(resolve => {
axios.get(`/wallet/user/${id}`)
.then((result) => {
resolve(
this.setState({
namaPertama: result.data.data.content[0].user.firstName
})
)
})
});
}
getHistoria = () => {
console.log(this.getNameAPI())
Promise.all([this.getNameAPI()])
.then(([result]) => {
this.setState({
namaKirim: result
})
})
axios.get(`/wallet/type/6?q=`)
.then((result) => {
this.setState({
historia: result.data.data.content
})
})
}
so i make a function that have a method get inside a promise, and when i console.log it in getHistoria, the promise give me an empty array..
can someone tell me what's wrong with my code? i just learning about the promise so i still don't really know about it, thank you..

You are returning the result of setState, I don't think setState is returning anything. Besides, setState is asynchronous, so you won't have the result of the updated state if you loged the state just after.
Just set the state with setState and return some value.
EDIT:
getNameAPI = () => {
const id = this.props.match.params.id;
return new Promise(resolve => {
axios.get(`/wallet/user/${id}`)
.then((result) => {
let value = result.data.data.content[0].user.firstName;
this.setState({ namaPertama: value})
resolve(value)
})
});
}

Related

How to wait until function returns a value?

I have a react class.
componentDidMount() {
let refresh_token = localStorage.getItem('refresh_token');
let isTokenActive = this.checkIfRefreshTokenWorking(refresh_token);
}
checkIfRefreshTokenWorking = (refresh_token) => {
let trueOrFalse = false;
fetch('http://localhost:8888/refresh_token?refresh_token='+refresh_token)
.then(response => {
response.json()
trueOrFalse = true;
})
.catch(err => {
trueOrFalse = false;
})
.finally(() => {
return trueOrFalse;
})
}
isTokenActive gets undefined. I want to wait until checkIfRefreshTokenWorking returns a value. Do I need to create a promise for this?
Javascript Promises
The function passed to new Promise is called the executor. When new Promise is created, the executor runs automatically. It contains the producing code which should eventually produce the result. In terms of the analogy above: the executor is the “singer”.
Its arguments resolve and reject are callbacks provided by JavaScript itself. Our code is only inside the executor.
This is not tested code
componentDidMount() {
let refresh_token = localStorage.getItem('access_token');
this.checkIfRefreshTokenWorking(refresh_token)
.then((data) => console.log(data))
.catch(error => console.log(error));
}
checkIfRefreshTokenWorking = (refresh_token) => new Promise((resolve, reject) => {
let trueOrFalse = false;
fetch('http://localhost:8888/refresh_token?refresh_token='+refresh_token)
.then(response => {
response.json()
trueOrFalse = true;
resolve(response.json());
})
.catch(err => {
trueOrFalse = false;
reject(err);
})
.finally(() => {
return trueOrFalse;
resolve();
})
})
You have two options here
Using async/await with refresh token call
Make use of conditional rendering way
// You need to use babel present env for this
async componentDidMount() {
const refresh_token = localStorage.getItem('access_token');
let isTokenActive = false;
try {
const response = awit fetch('http://localhost:8888/refresh_token?refresh_token=' + refresh_token);
const data = await response.json();
// console.log(data);
isTokenActive = true;
} catch(err) {
// Log the error
}
}
// Conditional rendering
this.state = {
...,
isLoading: false,
isTokenActive: false
};
componentDidMount() {
const refresh_token = localStorage.getItem('access_token');
this.setState({ isLoading: true });
fetch('http://localhost:8888/refresh_token?refresh_token=' + refresh_token)
.then(...)
.catch(...)
.finally(...); // Set the value for isLoading/isTokenActive with this.setState accordingly
}
render() {
const { isLoading, isTokenActive } = this.state;
!isLoading ? isTokenActive ? <MyComponent /> : <div /> : <div>Loading...</div>;
}
References:
https://www.valentinog.com/blog/await-react
https://reactjs.org/docs/conditional-rendering.html

How to wait firebase, fetch data and return array in method?

So i have this method in container component:
getProfilesOptions = () => {
const result = firebase.firestore().collection('roles').get().then(snapshot => {
const options = []
snapshot.docs.forEach(doc => {
options.push({ value: doc.id, label: doc.data().profile })
//console.log(doc.id) - everything ok, i'm fetching data correctyly
});
return options
})
console.log(result)//here i have pending result in debugger
return result
}
then, i'm passing link into child...child...child component.
Then i some child i want to call it, and get array as a result, and then set the state:
componentDidUpdate() {
if(this.state.isFocused && !this.state.options){
const options = this.props.getOptions()
this.setState({
options: options
})
}
}
Can i have a solution of this problem? Ofcourse i can pass props as result instead of props ref to the method, but can i use the method? How to improve getProfilesOptions?
You should wrap your firebase call in a Promise, because this is an async call.
getProfilesOptions = () => {
return new Promise(resolve => {
firebase.firestore().collection('roles').get().then(snapshot => {
const options = []
snapshot.docs.forEach(doc => {
options.push({ value: doc.id, label: doc.data().profile })
//console.log(doc.id) - everything ok, i'm fetching data correctyly
});
resolve(options)
})
}
}
And get the result in your component with .then()
componentDidUpdate() {
if(this.state.isFocused && !this.state.options){
this.props.getOptions().then(options => {
this.setState({
options: options
})
})
}
}
You can read more about Javascript Promises here

How to make asynchronous calls inside a forEach() which depends on data from another asynchronous call?

I am working with an API and want to make some calls from my React application. They are async calls nested inside a forEach(). I get all the promises and push them inside a promises array. Then use the axios.all() method as described by axios docs but when I push the results of these promises to the myData array I get an empty array.
Except axios.all(promises) method I tried nested then() calls on Promises but that just complicated everything. Here is my code:
componentDidUpdate(nextProps, nextState) {
if (this.props.to !== nextProps.to || this.props.from !==
nextProps.from) {
let promises = [];
Axios.get(
`http://localhost:3000/api/visits?from=${this.props.from}&to=${
this.props.to
}`
).then(res => {
res.data.forEach(visit => {
promises.push(
Axios.get(`http://localhost:3000/api/clients/${visit.clientId}`
})
);
});
Axios.all(promises).then(results => {
results.forEach(res => {
const clientProps = {
name: res.data[0].name,
lastname: res.data[0].lastname,
mobile_number: res.data[0].mobile_number
};
myData.push(clientProps); // Here I am pushing the data to a global array
});
this.setState({myData})
});
});
}
}
When I run the code I expect the array "myData" to be filled with the data pushed from the API call but instead I get an empty array. Is there any way to get around this problem?
// I try to access data from this.state inside the render() method of my class component to generate a Table data with the name property.
<td>{this.state.myData[index].name}</td>
I guess this version is more handy.
componentDidUpdate(nextProps, nextState) {
if (this.props.to !== nextProps.to || this.props.from !==
nextProps.from) {
let promises = [];
Axios.get(
`http://localhost:3000/api/visits?from=${this.props.from}&to=${
this.props.to
}`
).then(res => {
return Axios.all(res.data.map(visit => {
return Axios.get(`http://localhost:3000/api/clients/${visit.clientId}`)
}))
})
.then(results => {
return results.map(res => {
return {
name: res.data[0].name,
lastname: res.data[0].lastname,
mobile_number: res.data[0].mobile_number
};
});
})
.then(clientProps => {
// then update state or dispatch an action
this.setState(() => ({myData: clientProps}));
});
}
}
getVisits(from, to) {
return Axios.get(`http://localhost:3000/api/visits?from=${from}&to=${to}`);
}
getClients(ids) {
return Axios.all(ids.map(id => Axios.get(`http://localhost:3000/api/clients/${id}`));
}
async getClientsOfVisits(from, to) {
const response = await this.getVisits(from, to);
const promises = await this.getClients(response.data.map(visit => visit.clientId)));
return promises.map(res => res.data[0]);
}
componentDidUpdate(nextProps, nextState) {
const { to, from } = this.props;
const toChanged = to !== nextProps.to;
const fromChanged = from !== nextProps.from;
if (toChanged || fromChanged) {
this.getClientsOfVisits(to, from).then(myData => {
this.setState({ myData });
})
}
}

How to set state from Multiple API call?

I am having this issue with setState in the code.
Trying to do
what I wanted to achieve is to concatenate results from all the API calls into one variable in State.Should I assign everything in the first API (as in second or third API call wrap within the first API .then(function) ?)
or
should i define each api separately
var requestDigAPI = ...
var requestNEWSAPI =...
and call
this.setstate({
this.state.articles.concat(dig,buzzfeed)
})
What is the right approach?
ERRORs
No matter the method react logs error.
If I set the State in other API within the first API returns
error buzzfeed unidentified
or setState outside of both API
error dig , buzzfeed unidentified
componentDidMount() {
this.setState({loading: true})
var apiRequestDig = fetch("api").then(function(response) {
return response.json()
});
var apiRequestNews = fetch("api").then(function(response) {
return response.json()
})
var apiREquestBuzzFeed = fetch(api).then(function(response) {
return response.json()
})
var combinedData = {
"apiRequestDig": {},
"apiRequestNews": {},
"apiREquestBuzzFeed": {}
};
Promise.all([apiRequestDig, apiRequestNews, apiREquestBuzzFeed]).then(function(values) {
combinedData["apiRequestDig"] = values[0];
combinedData["apiRequestNews"] = values[1];
combinedData["apiREquestBuzzFeed"] = values[2];
return combinedData;
});
var dig = apiRequestDig.then(results => {
let dig = results.data.feed.map(article => {
return {
title: article.content.title_alt,
image: article.content.media.images[0].url,
category: article.content.tags[0].name,
count: article.digg_score,
description: article.content.description,
url: article.content.url
}
})
apiREquestBuzzFeed.then(results => {
console.log(results.big_stories[0].title)
let buzzfeed = results.big_stories.map(article => {
return {
title: article.title,
image: article.images.small,
category: article.category,
count: article.impressions,
description: article.description,
url: "https://www.buzzfeed.com"+article.canonical_path
}
})
})
this.setState({
articles: this.state.articles.concat(dig),
loading: "none"
})
// console.log(this.state);
})
}
thanks for the advice
You could chain your API calls, but Promise.all() allows you to make concurrent calls, so why not use it?
However, I think your API functions should be defined outside of componentDidMount, for more readability and reusability:
/* Outside of your component */
const apiRequest = url => fetch(url).then(response => response.json())
const apiRequestDig = () => {
return apiRequest("https://dig/api/url").then(results => {
return results.data.feed.map(article => {
return {
title: article.content.title_alt
/* ... */
};
});
});
};
const apiRequestNews = () => {
return apiRequest("https://news/api/url").then(results => {
return results.big_stories.map(article => {
return {
title: article.title
/* ... */
};
});
});
};
const apiRequestBuzzFeed = () => {
return apiRequest("https://buzzfeed/api/url").then(results => {
return results.big_stories.map(article => {
return {
title: article.title
/* ... */
};
});
});
};
/* Inside your component */
componentDidMount() {
this.setState({loading: true});
Promise.all([
apiRequestDig(),
apiRequestNews(),
apiRequestBuzzFeed()
]).then(values => {
return values[0].concat(values[1], values[2]);
}).then(results => {
this.setState({
articles: this.state.articles.concat(results),
loading: "none"
});
}).catch(err => {
console.log('Oops, something went wrong', err);
});
}
How about moving state manipulation code within the resolve callback of Promise.all ?
componentDidMount() {
this.setState({loading: true})
const apiRequestDig = fetch("api").then(response => response.json());
const apiRequestNews = fetch("api").then(response => response.json());
const apiREquestBuzzFeed = fetch("api").then(response => response.json());
Promise.all([
apiRequestDig,
apiRequestNews,
apiREquestBuzzFeed
]).then(([dig, news, feed]) => {
const digs = dig.data.feed.map(article => ({
title: article.content.title_alt,
image: article.content.media.images[0].url,
category: article.content.tags[0].name,
count: article.digg_score,
description: article.content.description,
url: article.content.url
}));
const buzzfeed = feed.big_stories.map(article => ({
title: article.title,
image: article.images.small,
category: article.category,
count: article.impressions,
description: article.description,
url: `https://www.buzzfeed.com${article.canonical_path}`
}));
this.setState({
articles: [...this.state.articles, ...digs],
loading: "none"
});
// return anything you want as wrapped with promise
return {
apiRequestDig: dig,
apiRequestNews: news,
apiREquestBuzzFeed: feed
};
});
.catch(e => {
// catch your error here
})
}
Using Promise.all will be the best approach.But keep in mind about the fail fast behavior of Promise.all where in if one request fails then Promise.all will reject immediately MDN Link. This behavior can be mitigated by catching the error and resolving with empty data.
function makeAPICall(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`).then(res => res.json());
}
var Posts = React.createClass({
getInitialState: function() {
return {};
},
componentDidMount: function() {
var requestsArray = [makeAPICall(1), makeAPICall(2), makeAPICall(3)];
Promise.all(requestsArray).then(values => {
var postTitles = values.map(post => post.title).join(", ");
this.setState({
data: postTitles
});
}).catch(console.error.bind(console));
},
render: function() {
return <div>{this.state.data}</div>;
}
});
ReactDOM.render(
<Posts/>,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Testing Redux Thunk Action Creator

I've got a redux action creator that utilizes redux-thunk to do some logic to determine what to dispatch to the store. Its not promise-based, like an HTTP request would be, so I am having some issues with how to test it properly. Ill need a test for when the value meets the condition and for when it doesn't. Since the action creator does not return a promise, I cannot run a .then() in my test. What is the best way to test something like this?
Likewise, I believe it would be pretty straightforward testing the getRemoveFileMetrics() action creator as it actually does return a promise. But how can I assert that that will called if the value is removeFiles and meets the condition? How can that be written in the test?
Thanks in advance as this has had me stuck for the last couple of days.
Action Creators
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
};
};
export const getRemoveFileMetrics = cacheKey => {
return dispatch => {
dispatch({ type: IS_FETCHING_DELETE_METRICS });
return axios
.get(`../GetRemoveFileMetrics`, { params: { cacheKey } })
.then(response => {
dispatch({ type: GET_REMOVE_FILE_METRICS, payload: response.data });
})
.catch(err => console.log(err));
};
};
Jest
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}]; // TypeError: Cannot read property 'then' of undefined
return store.dispatch(MOA.handleSelection(value)).then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});
NEW EDIT
So based off of Danny Delott's answer to return a promise, I acheived a passing test as follows:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
return new Promise((resolve, reject) => {
resolve(dispatch({ type: HANDLE_SELECTION, value }));
});
};
};
Is there a reason to explicitly NOT return a promise in your action creator? It looks like getRemoveFileMetrics is returning the promise, it just gets swallowed in handleSelection...
Easiest solution is to just return the promise:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
return new Promise();
};
};
Otherwise, you'll need make your assertions after the event loop is finished. You can do with a setTimeout wrapped in a Promise to get the .then behavior.
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}];
store.dispatch(MOA.handleSelection(value));
// flush outstanding async tasks
return new Promise(resolve => {
setTimeout(resolve, 0);
})
.then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});

Categories

Resources