Why does the UI not update for async/await function - javascript

This is my first website created with JavaScript. I'm trying to get items from API and then display them for the user.
My function:
getOrderItem = async (itemId: string) => {
console.log(await api.getItem(itemId))
return await api.getItem(itemId)
}
This is how I'm calling it in my UI:
<h4>Quantity: {item.quantity} Name: {this.getOrderItem(item.id).name}</h4>
My UI does not update but I can see that the function is working:
What I'm doing wrong with await/async?

try using the setState() hook with the useEffect() hook so you can tell React to re-render the DOM elements once your getOrderItem fulfills.
You can try setting it up like this:
const [getOrderItem, setOrderItem] = useState({})
getOrderItem = async (itemId: string) => {
console.log(await api.getItem(itemId))
return await api.getItem(itemId)
}
useEffect(() => {
setOrderItem(getOrderItem())
}, [])
return (
<h4>Quantity: {item.quantity} Name: {getOrderItem.name}</h4>
)

your function doesn't return anything until you get your response from the API. And by the time you get your response the UI is rendered already. Since the function doesn't update any state variables your UI doesn't re-render. You should use a state variable to store the response of the API.
getOrderItem = async (itemId: string) => {
let response = await api.getItem(itemId);
this.setState({items: {...this.state.items, itemId: response}});
}
Now, you can use the state variable to re-render
<h4>Quantity: {item.quantity} Name: {item.id in this.state.items ? this.state.items[item.id].name ? this.getOrderItem(item.id) }</h4>
If you only need the names of the items, you can store response.name instead of response

Related

How to access data outside of a Promise

I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>

How can I fix this problem in React Native Firebase?

I have the next problem:
export async function updateLessons() {
let data
await database().goOnline().then(async () => {
await database()
.ref('days')
.on('value', snapshot => {
console.log(snapshot.val())
data = snapshot.val();
});
});
return data;
}
I use this function to update my application when I swipe down
const onRefresh = React.useCallback(async () => {
setRefreshing(true);
setLessons(await updateLessons());
console.log(lessons)
setRefreshing(false);
}, []);
It is called from scroll view (refreshcontrol)
The problem is that it doesn't work asynchronously. In console log i see my snapshot. But the application updated faster and in console.log(lessons) it is undefined. How can I fix it?
Updating state is an asynchronous operation, and won't have completed yet by the time your console.log statement runs.
A simple way to get the correct output, is to capture the new lessons in local variable (which is updated synchronously):
const onRefresh = React.useCallback(async () => {
setRefreshing(true);
const newLessons = await updateLessons();
setLessons(newLessons);
console.log(newLessons)
setRefreshing(false);
}, []);
Also see:
The useState set method is not reflecting a change immediately, which also shows how to use a useEffect hook to respond to state changes.
React setState not updating state
Updated state values are not displaying in react

React: How to update a state prior to another function?

I have a state variable called list that updates when setList is called. SetList lives under the function AddToList, which adds a value to the existing values in the list. As of this moment, the function handleList executes prior to the state variable setList even though I have setList added prior to the function handleList. What I am trying to achieve is for the setList to update its list prior to running the handleList. Could you provide insights on how to fix this?
If you want to test the code, https://codesandbox.io/s/asynchronous-test-mp2fq?file=/Form.js
export default function Form() {
const [list, setList] = useState([]);
const addToList = (name) => {
let newDataList = list.concat(name);
setList(newDataList);
console.log("List: ", list);
handleList();
};
const handleList = async () => {
console.log("Handle List Triggered");
await axios
// .put("", list)
.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("Response: ", response);
})
.catch((error) => {});
};
return (
<AutoComplete
name="list"
label="Add to List"
onChange={(events, values) => {
addToList(values.title);
}}
/>
);
}
As you can tell, the get response is made prior to updating the list.
It's not clear what you want to do with the updated list, but you know what the new list will be, so you can just pass that around if you need it immediately.
const addToList = (name) => {
let newDataList = list.concat(name);
setList(newDataList);
console.log("List: ", list);
handleList(newDataList);
};
const handleList = async (list) => {
console.log("Handle List Triggered");
await axios
// .put("", list)
.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("Response: ", response);
})
.catch((error) => {});
};
React's useEffect hook has an array of dependencies that it watches for changes. While a lot of the time it's used with no dependencies (i.e. no second parameter or empty array) to replicate the functionality of componentDidMount and componentDidUpdate, you may be able to use that to trigger handleList by specifying list as a dependency like this:
useEffect(() => {
handleList(list);
}, [list]);
I think there may be a redundant request when page loads though because list will be populated which you'll most likely want to account for to prevent unnecessary requests.
first of all you have to understand setState is not synchronized that means when you call setList(newDataList) that not gonna triggered refer why setState is async
therefore you can use #spender solution
or useStateCallback hook but it's important understand setState is not sync
const [state, setState] = useStateCallback([]);
const addToList = (name) => {
........... your code
setList(newDataList, () => {
// call handleList function here
handleList();
});
}

How do I access the latest value of state using useSelector after dispatching an action in redux?

I have a screen in a React-Native project which essentially just renders a loading icon whilst fetching data from the server, before then taking the user to the main screen. The first function getPrivateKey() will return the private key and store it using redux in the state, and the next function connectWithKey() will then use that key to connect.
The issue I'm facing is that when connectWithkey() runs, it's using the initial, empty value of the private key, not the updated value. Here's the code, and apologies if I'm being stupid it's been a long day :(
export default DataLoader = props => {
//private key - this should, in theory, update after getPrivateKey()
const privateKey = useSelector(({ main }) => main.privateKey);
const dispatch = useDispatch();
useEffect(() => {
const configure = async () => {
//this will update the private key
await getPrivateKey();
//this should use the new private key from useSelector, but instead is using the initialised empty object
await connectWithKey();
props.navigation.navigate('MainScreen');
};
configure();
}, []);
//.... more code below....
I've tried adding privateKey into the array dependencies which just caused an infinite loop, and I've checked that the value has updated in the redux store - so I'm a bit lost! In essence, it appears that the useSelector hook isn't getting a fresh value. Any help would be very much appreciated 😊 Thanks!
EDIT - added more code upon request 😊
const getPrivateKey = async () => {
const privKey = await fetchKeyFromServer();
dispatch({
type: 'UPDATE',
value: privKey
});
};
const connectWithkey = async () => {
//the privateKey here should be the updated value from useSelector
await connectToServer(privateKey)
};
Looks like your getPrivateKey function is a thunk, but you are not dispatching it ? And there is nothing stopping you from returning values from thunks.
const getPrivateKey = async (dispatch) => {
const privKey = await fetchKeyFromServer();
dispatch({
type: 'UPDATE',
value: privKey
});
return privKey // return the key here to whoever wants to use the value immediately.
};
Then in your useEffect in the component you can use the return value easily :)
useEffect(() => {
const configure = async () => {
//make sure you 'dispatch' this thunk
const key = await dispatch(getPrivateKey());
// pass the key
await dispatch(connectWithKey(key));
...
};
....
}, []);
The code above assumes that the connectWithKey is a thunk too. If so, you can design the thunk in a way that it either uses the passed value or reads it from the redux store.
const connectWithkey = (privateKey: passedPrivateKey) = async (dispatch, getState) => {
const state = getState();
let privateKey = state.whatever.the.path.is.to.privateKey;
// use the passed private key if it is present.
if (passedPrivateKey) {
privateKey = passedPrivateKey;
}
await connectToServer(privateKey)
};
I have used this approach several times in my app. This way you do not need to rely on the state in the selector. And should you choose to rely on that state, the dependencies of your useEffect should update accordingly. Right now it is an empty array, and that is why the effect doesn't run again on any state changes (it is acting like the componentDidMount lifecycle function).
const privateKey = useSelector(({ main }) => main.privateKey);
useEffect(() => {
await getPrivateKey();
if (privateKey) {
await connectWithKey();
}
}, [privateKey]);
This way your hook re-runs everytime privateKey state changes. You might need to have some sort of condition for your connectWithKey thunk though, so that it doesn't run if the key is null.

Items being displayed when query is empty in react

I am learning react, and I have made a sample tv show app using an example off freecodecamp. It is all working okay from what I can see however after searching for something and then backspacing everything in the search box, it shows results when it shouldn't be, can anyone see a mistake I have made in my code?
class SeriesList extends React.Component {
state = {
series: [],
query: ''
}
onChange = async e => {
this.setState({query: e.target.value});
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${this.state.query}`
);
const data = await response.json();
this.setState({series: data});
}
render(){
return (
<div>
<input type="text" onChange={this.onChange} value={this.state.query} />
<ul>
<SeriesListItem list={this.state.series} />
</ul>
</div>
);
}
}
I have it on codepen here.
https://codepen.io/crash1989/pen/ERxPGO
thanks
One else point you can use await for setState
onChange = async e => {
await this.setState({query: e.target.value});
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${this.state.query}`
);
const data = await response.json();
this.setState({series: data});
}
Your request will be performed after changing query
setState works async. So there is no guarantee, that this.state.query has been updated after calling this.setState({query: e.target.value}). So your url contains the previous state eventually.
There are two options:
Use the event data for the new query:
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${e.target.value}`
);
...
Use the setState callback second arg
this.setState({query: e.target.value}, () => {
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${this.state.query}`
);
...
})
This is a good article about the async nature of setState https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3.
Problem
There are race conditions: When you type two+ letters you make two+ network calls. The first network call may be last to complete, and then you show results for the wrong query.
I fixed it in this pen: https://codepen.io/arnemahl/pen/zaYNxv
Solution
Keep track of results for all queries you have ever gotten the response to. Always how the data returned for the current query. (Also, don't make multiple API calls for the same query, we already know).
state: {
seriesPerQuery: {}, // Now a map with one result for each query you look up
query: [],
}
...
onChange = async e => {
...
// Now keep the results of the old querys, in addition to the new one
// whenever you get an API response
this.setState(state => ({
seriesPerQuery: {
...state.seriesPerQuery,
[query]: data,
}
}));
}

Categories