promise chaining and async, await - reactjs

I have been stuck on this for a while.
In React component I am trying to fetch data from 3 sources and in addition on fetch url depends on another fetch result. Then I'd like to use Promise.all function to setState just one for all three fetch functions.
I have tried to use async/await combination in every possible way but still promises are pending. What am I missing?
class DuelDetail extends React.Component {
state = {
duel: [],
dataset: [],
algorithms: []
};
fetchDuel = async () => {
var dataset = null
const duelID = this.props.match.params.duelID;
let duel = await axios.get(`http://127.0.0.1:8000/api/duel/${duelID}`,
{'headers': {'Authorization': `Token ${localStorage.getItem('token')}`}})
.then(async (res) => {
dataset = await this.fetchDataset(res.data.dataset)
return [duel, dataset]
}
)
};
fetchDataset = async (datasetId) => {
let res = await axios.get(`http://127.0.0.1:8000/api/dataset/${datasetId}`,
{'headers': {'Authorization': `Token ${localStorage.getItem('token')}`}})
return res
};
fetchAlgorithms = () => {
return axios.get(`http://127.0.0.1:8000/api/algorithm/`,
{'headers': {'Authorization': `Token ${localStorage.getItem('token')}`}})
};
componentDidMount() {
const promises = [this.fetchDuel()[0], this.fetchDuel()[1], this.fetchAlgorithms()]
Promise.all(promises).then(([duelsResponse, datasetsResponse, algorithmsResponse]) => {
this.setState({
duels: duelsResponse.data,
dataset: datasetsResponse.data,
algorithms: algorithmsResponse.data
});
});
}

Related

REACT JS - Not able to set the state of an empty object to the response from the api

I am fetching json data from my local api but can't seem to assign it to the err state.
const [err, setErr] = useState({});
const host = "http://localhost:9000";
const addNote = async (obj) => {
const res = await fetch(`${host}/api/notes/addnote`, {
method : 'POST',
headers : {'Content-Type': 'application/json', 'auth-token' : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiQGdtYWlsLmNvbSIsImlhdCI6MTY3MTQ0ODMyOH0.sNTvl4L9HFaGPOmuSKpJMu418axsUmgDib-94ked3lQ'},
body : JSON.stringify(obj)
});
const data = await res.json();
console.log(data);
setErr(data);
console.log(err);
}
On logging data I get => {title:{msg :''}, description:{msg:''}}
On logging err I get => {}
useState() hook is asynchronous and will not reflect the update immediately. The value get's update in the next render and you can verify that with useEffect hook as shown below
useEffect(() => {
console.log(err) // prints the updated value
}, [err])
If you want the update to reflect immediately, you can use useRef() instead of useState().
Updated answer
I don't recommend using useRef() as it would force the updates between the renders which in turn effects the performance. If you want the errors to be displayed based on backend response, then don't render the component until you receive a response. Refer below code snippet
const [err, setErr] = useState({});
const [isDataLoading, setIsDataLoading] = useState(false);
const host = "http://localhost:9000";
const addNote = async (obj) => {
setIsDataLoading(true); // update loading state
const res = await fetch(`${host}/api/notes/addnote`, {
method : 'POST',
headers : {'Content-Type': 'application/json', 'auth-token' : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiQGdtYWlsLmNvbSIsImlhdCI6MTY3MTQ0ODMyOH0.sNTvl4L9HFaGPOmuSKpJMu418axsUmgDib-94ked3lQ'},
body : JSON.stringify(obj)
});
const data = await res.json();
setErr(data);
setIsDataLoading(false); // update loading state
console.log(data);
}
return (
{
isDataLoading ? <Loading /> : <YourComponent /> // if isDataLoading is true render some loading symbol else render your actual component.
}
)
const [err, setErr] = useState({});
const host = "http://localhost:9000";
const addNote = async (obj) => {
try {
const res = await fetch(`${host}/api/notes/addnote`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"auth-token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiQGdtYWlsLmNvbSIsImlhdCI6MTY3MTQ0ODMyOH0.sNTvl4L9HFaGPOmuSKpJMu418axsUmgDib-94ked3lQ",
},
body: JSON.stringify(obj),
});
const data = await res.json();
if (res.status !== 200) {
// need to check object and set error as per object mappig
setErr(res.data);
}
} catch {
setErr('Api not workoing');
}
console.log(err);
};

Adding multiple API calls to a map in reactjs using axios

I need my API call to pull NFT data from moralis and add it to a map so it can later be rendered.
This all works fine, however the limit per call on moralis is 100 lines. I have added a second API call using cursor pagination. Both API calls work individually but when I try to add both to the map it just renders the most recent one. Is there a way to show everything in the collection? Thanks in advance!!
Here is the code I currently have to call the API:
async function callApi() {
var provider = await web3Modal.connect();
web3 = new Web3(provider);
await provider.send('eth_requestAccounts');
var accounts = await web3.eth.getAccounts();
account = accounts[0];
vaultcontract = new web3.eth.Contract(VAULTABI, STAKINGCONTRACT);
let config = { 'X-API-Key': moralisapikey, 'accept': 'application/json', cursor: '' };
const nfts0 = await axios.get((moralisapi + `nft/${NFTCONTRACT}/owners?chain=polygon&format=decimal&limit=100`), { headers: config })
.then(output => {
const { result } = output.data
return result;
})
const nfts1 = await axios.get((moralisapi + `nft/${NFTCONTRACT}/owners?chain=polygon&format=decimal&limit=100`), { headers: config })
.then(output => {
const { result } = output.data
return result;
})
const nfts = (nfts0, nfts1)
const apicall = await Promise.all(nfts.map(async i => {
let item = {
tokenId: i.token_id,
holder: i.owner_of,
wallet: account,
}
return item
}))
const stakednfts = await vaultcontract.methods.tokensOfOwner(account).call()
.then(id => {
return id;
})
const nftstk = await Promise.all(stakednfts.map(async i => {
let stkid = {
tokenId: i,
}
return stkid
}))
getNfts(apicall)
getStk(nftstk)
console.log(apicall);
setLoadingState('loaded')
}

React : waiting for a promise

i got a function that uses axios to get info from my backend as follows:
const getDoH = async () => {
const user = JSON.parse(localStorage.getItem("user"));
let config = {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + user.accessToken,
},
params: {
username: user.username,
},
};
return await axios.get(API_URL + "get", config);
};
Now i want to save these values into a global variable on initiate so i use the initialstate hook like this:
const [initialValues, setInitialValues] = useState(() => {
const initialSwitchState = getDoH().then(
(response) => {
setInitialValues(response.data);
},
(error) => {
console.log(error);
}
);
return initialSwitchState;
});
after that i got a function that takes the values from the db and maps them onto my local variable and this function looks like this:
const setStartValues = () => {
let newValues = initialSwitchState;
let valueArray = Object.entries(newValues).map((v, index) => {
v[1] = initialValues.switchValues[index]
return v
});
newValues = Object.fromEntries(valueArray);
setValues({...newValues});
}
and i want to call this function with a final function that is another initialstate hook like this:
const [values, setValues] = useState(() => {
const initialState = setStartValues();}
but by the time it gets to the line:
v[1] = initialValues.switchValues[index]
the initialValues is still a promise. and i cant see where i have gone wrong as i have used async and wait on my initial getDoH() function.
How can i solve this (wait for the promise) before i try to use the results?
kind regards.
There are two issues here:
First, you need to await getDoH() since that's an async function.
Second, useState() is a synchronous function, so you need to do the await getDoH() inside a useEffect() before you set const [initialValues, setInitialValues] = ...
Tbh i did it with use effect and it comes with its own set of issues. found the best way to do it was:
const [values, setValues] = useState(async () => {
const initialState = await getDoH().then(
(response) => {
let newValues = switchState;
let valueArray = Object.entries(newValues).map((v, index) => {
v[1] = response.data.switchValues[index]
return v
});
newValues = Object.fromEntries(valueArray);
setValues({...newValues});
},
(error) => {
console.log(error);
}
);
return initialState;
});

What do I put in my return if I want to return the response from an api call react?

I needed to add headers to my api call. I tried doing this many different ways to no avail. This is my original api call without the header:
export default async function FetchPageMarkdown(page: string): Promise<string> {
const baseUrl = getBackendUrl();
let response = await fetch(`${baseUrl}/api/pagemarkdown/${page}`);
let text = await response.text();
return text
}
This is how I am trying to add the header:
const FetchPageMarkdown = (page: string): Promise<string> => {
const { getAccessTokenSilently } = useAuth0();
const callSecureApi = async () => {
const token = await getAccessTokenSilently();
const baseUrl = getBackendUrl();
const response = await fetch(
`${baseUrl}/api/pagemarkdown/${page}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const text = await response.text();
return text;
}
};
export default FetchPageMarkdown;
Without having a return I get a function whose declared type is neither 'void' nore 'any' must return a value. I want to return the text coming from the api call which is markdown. If I put the return outside the callSecureApi function it cannot find text.
Have you tried calling and returning the result of the asynchronous function, i.e. return callSecureApi();?
Since useAuth0 is a React hook it can't be called from functions (breaks rules of hooks), but its getAccessTokenSilently function can be passed around.
const fetchPageMarkdown = (getAccessTokenSilently: () => string, page: string): Promise<string> => {
const callSecureApi = async () => {
const token = await getAccessTokenSilently();
const baseUrl = getBackendUrl();
const response = await fetch(
`${baseUrl}/api/pagemarkdown/${page}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const text = await response.text();
return text;
}
return callSecureApi(); // invoke and return Promise
};
Usage:
const { getAccessTokenSilently } = useAuth0();
fetchPageMarkdown(getAccessTokenSilently, page)
.then(text => {
// handle returned text
});

how to update a state variable immediately

How should I code so that I can save the API response to the state immediately and access the updated response outside the setState().
state={
response: []
}
this.setState({
response: res // res is coming from API response
});
console.log(this.state.response); // it should show the API response instead of empty []
Use callbacks:
...
this.setState({
response: res // res is coming from API response
}, function() {
console.log(this.state.response);
});
...
Make the method calling setState to be async, and let res await answer. Something like
yourMethod = async (params) => {
const res = await //api call
this.setState({
response: res
)}
}
componentDidMount is the recommended lifecycle hook to fetch initial state data
class App extends React.Component {
state = {
data: [],
}
async componentDidMount() {
const res = await fetch(url);
const data = await res.json();
this.setState({ data }, console.log('state updated', this.state));
}
render() {
return (
/* render data */
);
}
}

Resources