setState is not working on 2nd time calling it inside componentDIdMount - javascript

im a beginner, im trying to make a memory game
this component fetches data for an api
then trims it down with only that has image link
then on level one it should display 3 random image from fetch data
it always
displayedChars: [undefined, undefined, undefined]
constructor(props) {
super()
this.state = {
level: 1,
numImg: 1*3,
displayedChars: [],
chars: []
}
}
async componentDidMount() {
await this.loadData().then(data => {
this.setState({
chars: this.trimData(data)
});
});
await this.displayChars().then(data => {
console.log(data)
this.setState({
displayedChars: data
});
});
console.log(this.state);
}
async loadData() {
try {
const res = await fetch(`http://hp-api.herokuapp.com/api/characters`)
const characters = await res.json();
return characters
} catch(err) {
console.error(err)
}
}
trimData(characters) {
const listChars = []
characters.map(char => {
if(char.image !== "") {
listChars.push(char)
}
})
return listChars
}
displayChars() {
return (new Promise((resolve) => {
const list = []
for(let x=1; x<= this.state.numImg; x++) {
let randomNum = Math.floor(Math.random() * 24);
list.push(this.state.chars[randomNum]);
}
console.log(list)
resolve(list)
}))
}
in the this.displayChars()
console.log(data) works fine
but
this.setState({
displayedChars: data
});
then console.log(this.state)
OUTPUT: [undefined, undefined, undefined]

setState is async, so you cannot see the updated states immediately, that's why your console.log is [undefined, undefined, undefined]. You can assign variables to handle responses separately instead of using this.state.
The second concern is you shouldn't mix then and async/await. I'd prefer using async/await alone in your case.
async componentDidMount() {
const chars = await this.loadData();
const displayedChars = await this.displayChars();
this.setState({
displayedChars: displayedChars
chars: this.trimData(chars)
});
//states are not updated right away
//console.log(this.state);
//access via variable
console.log({ chars, displayedChars })
}

Related

Array of strings getting converted to Objects

I'm pushing files to amazon using pre-signed URLs, and modifying the files array with the file name reference inside the newData object. (The files array are inside an array of objects called items)
// Add job
const addJob = async(data, user) => {
const newData = { ...data };
data.items.map((item, itemIndex) => {
if (item.files !== []) {
item.files.map(async(file, fileIndex) => {
const uploadConfig = await axios.get(`/api/s3upload`, {
params: {
name: file.name,
},
});
console.log(uploadConfig.data.key);
newData.items[itemIndex].files[fileIndex] = uploadConfig.data.key;
await axios.put(uploadConfig.data.url, file);
});
}
});
console.log(newData);
try {
const res = await axios.post('/api/jobs', newData);
dispatch({
type: ADD_JOB,
payload: res.data,
});
} catch (error) {
console.log(error);
}
};
The file references comes in the uploadConfig.data.key and are being save into the newData object.
When this function is executed, something peculiar happens:
the console log of newData returns the correct array of references to the files
the files are uploaded just fine
the request made to /api/jobs, which is passing newData, sends an array of objects that contains { path: ... }
console.log(newData):
Post request:
JavaScript does this because forEach and map are not promise-aware. It cannot support async and await. You cannot use await in forEach or map.
for loops are promise-aware, thus replacing the loops with for loops and marking them as await returns the expected behaviour.
source: zellwk article
Corrected (functioning) code:
const addJob = async (data, user) => {
const newData = { ...data };
const { items } = data;
const loop = async () => {
for (let outer in items) {
if (items[outer].files !== []) {
const loop2 = async () => {
for (let inner in items[outer].files) {
const uploadConfig = await axios.get(`/api/s3upload`, {
params: {
name: items[outer].files[inner].name,
},
});
const res = await axios.put(uploadConfig.data.url, items[outer].files[inner])
newData.items[outer].files[inner] = uploadConfig.data.key;
}
};
await loop2();
}
}
};
await loop();
try {
const res = await axios.post('/api/jobs', newData);
dispatch({
type: ADD_JOB,
payload: res.data,
});
} catch (error) {
console.log(error);
}
};

How can I wait until the functions finish in Reactjs?

Hi I am new to reactjs and I am trying to build button with a function doing some calculation by Reactjs. The logic is, first I will get two lists from database by two functions. After these 2 functions return results and setState, the calculate function will continue and do its job. But somehow the state is not being updated and it will crash. How can I secure the state is being updated before to the calculate? Thanks a lot!
Code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dividendList : [],
divisorList : [],
};
}
getDividend(){
var self = this;
axios.post(SERVER_NAME + 'api/getDividend', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ dividendList : results.data})
})
.catch(function(err){
console.log(err)
});
}
getDivisor(){
var self = this;
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ divisorList : results.data})
})
.catch(function(err){
console.log(err)
});
}
doCal = () => {
var self = this;
self.getDividend();
self.getDivisor();
const { dividendList , divisorList} = self.state;
# then will loop the list and do math
# but since the state is not update, both lists are empty []
}
Tried Promise;
getDivisor(){
var self = this;
return new Promise((resolve, reject) => {
axios.post(SERVER_NAME + 'api/draw/getDivisor', {})
.then(function(response){
resolve(response)
})
.catch(function(err){
resolve();
});
})
}
I think the issue here is self.getDividend(); and self.getDivisor(); are async operations. They will take some time to complete. By the time you hit the next line const { dividendList , divisorList} = self.state;, these operations are not complete and you will end up getting empty lists.
One way to address this is using moving your doCal function logic after getDividend and getDivisor are completed. You can also execute these in parallel instead of in a sequence. I used async format instead of .then(). It is just a sysntatic sugar. You can achieve the same using .then() if you prefer that way
async function doCalc() {
const prom1 = axios.get('https://..dividentList');
const prom2 = axios.get('https://..divisorList');
const results = await Promise.all([ prom1, prom2]); // wait for both promise to complete
// look inside results to get your data and set the state
// continue doCal logic
}
Using .then()
request1('/dividentList')
.then((res) => {
//setState for divident
return request2('/divisorList'); // this will return a promise to chain on
})
.then((res) => {
setState for divisor
return Promise.resolve('Success') // we send back a resolved promise to continue chaining
})
.then(() => {
doCalc logic
})
.catch((err) => {
console.log('something went wrong');
});
I looked at your code and thought it should be changed like this to be correct.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dividendList: [],
divisorList: [],
};
}
componentDidMount() {
// the API just need be called once, so put here
this.getDividend()
this.getDivisor()
}
componentDidUpdate(_, prevState) {
const { dividendList , divisorList } = this.state;
// Ensure that the answer is only calculated once
// the answer is only be calculated while the two list data are obtained
if (
prevState.divisorList.length === 0 &&
prevState.dividendList.length === 0 &&
divisorList.length > 0 &&
dividendList.length > 0
) {
doCal()
}
}
getDividend(){
var self = this;
axios.post(SERVER_NAME + 'api/getDividend', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ dividendList : results.data})
})
.catch(function(err){
console.log(err)
});
}
getDivisor(){
var self = this;
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ divisorList : results.data})
})
.catch(function(err){
console.log(err)
});
}
doCal = () => {
const { dividendList , divisorList } = this.state;
# then will loop the list and do math
# but since the state is not update, both lists are empty []
this.setState({ answer: 'xxx' })
}
render() {
const { dividendList, divisorList, answer } = this.state
if (dividendList.length === 0 && divisorList.length === 0) {
return <div>Loading...</div>
}
if (!answer) {
return <div>Error</div>
}
return <div>{answer}</div>
}
}
The following are just some suggestions to make the code easier to read,
you can use arrow function so that you don't need to write self.setState({...})
getDividend = () => {
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then((response) => {
let results = response.data;
console.log(results)
this.setState({ divisorList : results.data})
})
.catch((err) => {
console.log(err)
});
}
and you can also use async/await instead of promise.then
getDividend = async () => {
const response = await axios.post(SERVER_NAME + 'api/getDivisor', {})
let results = response.data;
console.log(results)
this.setState({ divisorList : results.data})
}
Set 'dividendList' and 'divisorList' equals to 'null' by default. Then, when a function that uses those lists is called, make a if statement to verify if those states goes for false (if they are still null) then return inside the function, if not, it should not crash anything.

AsyncStorage.getItem returns undefined(even with .then and .parse)

I'm attempting to store data in AsyncStorage and load them back(obviously). The .setItem function works, and the notification pops up at the bottom of the iOS simulator when I call it. However, the .getItem function doesn't work, and when I console.log it, returns undefined. I have two functions to store and fetch the data:
setData = (rawDataToStore, keyToStore) => {
data_store = JSON.stringify(rawDataToStore);
AsyncStorage.setItem(keyToStore, data_store, () => {
console.warn('Stored data!')
} )
}
getData = (keyToSearch) => {
AsyncStorage.getItem(keyToSearch).then(storage => {
parsed_data = JSON.parse(storage);
return parsed_data
}).catch(e => console.warn(e))
}
I just tested the functions in my render():
to save the data:
this.setData({value: 1}, "test_data");
to load the data:
console.log(this.getData("test_data"));
The console.log just returns undefined.
I'm completely new to Asyncstorage, but what am I doing wrong?
Your console.log returns undefined ... cause, your setData function hasn't finished its job yet ... you have to await for it first, cause it's an async operation.
class YourComponent extends React.Component {
async componentDidMount() {
await this.setData();
const data = await this.getData();
console.log('data returned', data);
}
setData = async (rawDataToStore, keyToStore) => {
data_store = JSON.stringify(rawDataToStore);
await AsyncStorage.setItem(keyToStore, data_store);
};
getData = async keyToSearch => {
let parsed_data = null;
const storage = await AsyncStorage.getItem(keyToSearch);
if (storage) {
parsed_data = JSON.parse(storage);
console.log('Data in AsyncStorage: ', parsed_data);
}
return parsed_data;
};
}

How to use Fetch queries in a loop?

I make a request to the server via a map with different urls, then I set the data in State and use it for output. I want the requests to be consecutive but sometimes they do not work correctly and get bugs, how to write the code for normal data retrieval?
const urlList = ["countries", "states", "cities", "users"];
componentDidMount() {
urlList.map( (url, index) => {
return servicesAPI.getResourse(url).then( (body) => {
index !== 3 ? this.setState({
dataAPI : [...this.state.dataAPI, body] }) :
this.setState({
dataAPI : [...this.state.dataAPI, body],
loaded: true
})
})
})
export default class ServicesAPI {
_apiBase = `http://localhost:3001/`;
async getResourse(url) {
const res = await fetch(`${this._apiBase}${url}`);
if (!res.ok) {
throw new Error(`Could not fetch ${url}` +
`, received ${res.status}`)
}
return await res.json();
}
Use of Promise.all();
componentDidMount() {
const fetchPromises = [];
urlList.forEach( (url, index) => {
fetchPromises.push(servicesAPI.getResourse(url));
});
const allResourcesPromise = Promise.all(fetchPromises);
allResourcesPromise.then(data => {
// list with responses
}).catch(err => {
console.log(err.toString());
});
}
Sample example:
https://jsbin.com/vevufumano/1/edit?html,js,console,output
Also instead of then, where is possible, you can use async/await for more cleaner code.

Async/await in componentDidMount to load in correct order

I am having some troubles getting several functions loading in the correct order. From my code below, the first and second functions are to get the companyID companyReference and are not reliant on one and another.
The third function requires the state set by the first and second functions in order to perform the objective of getting the companyName.
async componentDidMount() {
const a = await this.companyIdParams();
const b = await this.getCompanyReference();
const c = await this.getCompanyName();
a;
b;
c;
}
componentWillUnmount() {
this.isCancelled = true;
}
companyIdParams = () => {
const urlString = location.href;
const company = urlString
.split('/')
.filter(Boolean)
.pop();
!this.isCancelled &&
this.setState({
companyID: company
});
};
getCompanyReference = () => {
const { firebase, authUser } = this.props;
const uid = authUser.uid;
const getUser = firebase.user(uid);
getUser.onSnapshot(doc => {
!this.isCancelled &&
this.setState({
companyReference: doc.data().companyReference
});
});
};
getCompanyName = () => {
const { firebase } = this.props;
const { companyID, companyReference } = this.state;
const cid = companyID;
if (companyReference.includes(cid)) {
const getCompany = firebase.company(cid);
getCompany.onSnapshot(doc => {
!this.isCancelled &&
this.setState({
companyName: doc.data().companyName,
loading: false
});
});
} else if (cid !== null && !companyReference.includes(cid)) {
navigate(ROUTES.INDEX);
}
};
How can I achieve this inside componentDidMount?
setState is asynchronous, so you can't determinate when the state is updated in a sync way.
1)
I recommend you don't use componentDidMount with async, because this method belongs to react lifecycle.
Instead you could do:
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const a = await this.companyIdParams();
const b = await this.getCompanyReference();
const c = await this.getCompanyName();
}
2)
The companyIdParams method doesn't have a return, so you are waiting for nothing.
If you need to wait I would return a promise when setState is finished;
companyIdParams = () => {
return new Promise(resolve => {
const urlString = location.href;
const company = urlString
.split('/')
.filter(Boolean)
.pop();
!this.isCancelled &&
this.setState({
companyID: company
}, () => { resolve() });
});
};
The same for getCompanyReference:
getCompanyReference = () => {
return new Promise(resolve => {
const { firebase, authUser } = this.props;
const uid = authUser.uid;
const getUser = firebase.user(uid);
getUser.onSnapshot(doc => {
!this.isCancelled &&
this.setState({
companyReference: doc.data().companyReference
}, () => { resolve() });
});
});
};
3)
If you want to parallelize the promises, you could change the previous code to this:
const [a, b] = await Promise.all([
await this.companyIdParams(),
await this.getCompanyReference()
]);
4)
According to your code, the third promise is not a promise, so you could update (again ;) the above code:
const [a, b] = .....
const c = this.getCompanyName()
EDIT: the bullet points aren't steps to follow
As the last api call is dependent on the response from the first 2 api calls, use a combination of Promise.all which when resolved will have the data to make the last dependent call
async componentDidMount() {
let [a, c] = await Promise.all([
this.companyIdParams(),
this.getCompanyReference()
]);
const c = await this.getCompanyName();
}

Categories