I'm working in a project where I have to modify a value certain number of times before I make a request to an API. The problem is that when I'm using hooks to update the value, when I'm trying to update the value, as useState is asynchronous, the update of the value stays in a past value. However the value get modified after doing the request.
How can I make that the value updated before my request?
Here is the code:
useEffect(() => { // I'm using a useEffect hook to verify that my variable is updated. But that update is done late.
console.log(valorTotal);
}, [valorTotal]);
const agregarPlato = async () => {
if(validarCamposPlato()){
try{
let valorParcial = 0;
let platoExist = await encontrarPlato(config, rvPlato);
if(platoExist === true){
setAgregadoMin(true);
platoCodigo = await obtenerCodigoPlato(config, rvPlato);
platosRegVent.push({codigoPlato: platoCodigo, cantidad: rvCantidad});
let costoPlato = await obtenerValorPlato(config, rvPlato);
valorParcial = valorTotal;
setValorTotal(valorParcial += (costoPlato * parseInt(rvCantidad))); // Here is where I'm changing the value of my variable.
setRvPlato('');
setRvCantidad('');
}
else{
toast.error('The object wasn't found.');
setRvPlato('');
}
}
catch(error){
toast.error('An unexpected error has ocurred');
props.handleSubmit();
}
}
}
const finalizarRegVent = async () => {
console.log(agregadoMin);
if(validarCampos()){
try{
if(rvPlato !== '' || rvCantidad !== ''){
agregarPlato(); // Here I'm calling the function above
}
if(agregadoMin === true){
rvCodigo = await crearRegistroVenta(config, valorTotal, fechaActual, regVentMesa); // Here I'm doing the request to save the value
platosRegVent.forEach( (plato : any) => {
crearRegVentPlato(config, rvCodigo, platosRegVent.codigoPlato, platosRegVent.cantidad);
});
valorFinal = true;
}
else{
toast.error('You have to add an object before doing this option.');
}
}
catch(error){
toast.error('An unexpected error had happened.');
props.handleSubmit();
}
}
}
Thank you for your help.
Please try to use await in front of the calling function.
if(rvPlato !== '' || rvCantidad !== ''){
await agregarPlato();
}
And write below code inside of hook event.
useEffect(() => {
if(agregadoMin === true){
rvCodigo = await crearRegistroVenta(config, valorTotal, fechaActual, regVentMesa);
...
} else {
toast.error('You have to add an object before doing this option.');
}
}, [agregadoMin])
Then if agregadoMin is changed, hook will monitor changes and execute accordingly
Hope this helps you to understand.
Related
Now I am aware that there are many of questions that asked the same thing. But I also found many that implemented the right methods but nothing worked for them even peoples' answers
Basically, I wanted to use AsyncStorage to save a few user preferences. At first everything worked and was saved correctly, but then suddenly nothing worked anymore.
I kept trying and trying, and made a very interesting finding.
First here's my code:
My import:
import AsyncStorage from '#react-native-async-storage/async-storage';
Default State:
state : AppState = {
messages: [],
isMuted: false
}
This is my getter. It works on init:
componentDidMount() {
this.getSettings();
}
async getSettings() {
try {
AsyncStorage.getItem("muted").then((muted)=> {
if (muted != null) {
this.setState({"isMuted": eval(muted)});
console.log("init! "+this.state.isMuted.toString());
} else {
console.log("init! found null");
}
})
} catch(e) {
// error reading value
}
}
Here's my setter, it works onPress of a button
onPressSpeaker = async () => {
var muted = !this.state.isMuted;
this.setState({"isMuted": muted});
try {
await AsyncStorage.setItem("muted", this.state.isMuted.toString());
console.log("saved! "+this.state.isMuted.toString());
const muted = await AsyncStorage.getItem('muted');
if(muted !== null) {
console.log("data found! "+this.state.isMuted.toString());
}
} catch (e) {
console.log("error")
}
};
I believe I set everything correctly.
But here's my log (from Flipper)
20:57:41.654
init! true
20:57:44.247
saved! false
20:57:44.256
data found! false
20:58:04.788
Running "Voice Message" with {"rootTag":51}
20:58:05.800
init! true
The last init was supposed to return the new value but it keeps returning the old value again and again, everytime I refresh (restart) the application.
Did I do something wrong? Am I missing something? Is there something I need to know about react-native-async-storage?
I think the problem that you are storing the this.state.isMuted value before the state mutates
To better understand you can try this code
onPressSpeaker = async () => {
var muted = !this.state.isMuted;
this.setState({"isMuted": muted});
try {
//Here we are trying to log the state before Add it to Storage
console.log('State => Before AsyncStorage.setItem', this.state.isMuted)
await AsyncStorage.setItem("muted", this.state.isMuted.toString());
console.log("saved! "+this.state.isMuted.toString());
const muted = await AsyncStorage.getItem('muted');
if(muted !== null) {
console.log("data found! "+this.state.isMuted.toString());
}
} catch (e) {
console.log("error")
}
};
Your log will now be like this
20:57:41.654
init! true
20:57:44.247
'State => Before AsyncStorage.setItem' true
20:57:44.247
saved! false
20:57:44.256
data found! false
Solution: So you need to write the function in the callback to the setState function
storeIsMuted = async () => {
try {
console.log("before setItem", this.state.isMuted.toString());
await AsyncStorage.setItem("muted", this.state.isMuted.toString());
console.log("saved! " + this.state.isMuted.toString());
//
const muted = await AsyncStorage.getItem("muted");
if (muted !== null) {
console.log("data found! " + this.state.isMuted.toString());
}
} catch (e) {
console.log("error");
}
};
onPressSpeaker = () => {
var muted = !this.state.isMuted
this.setState({ isMuted: muted }, async () => this.storeMuted());
};
Documentation
SetState
Despite looking and following numerous answers here at stackoverflow,I have still failed to refactor this code to abide by the ESLint no-loop-func.
I keep getting the following warning, despite my efforts to refactor the code:
Compiled with warnings.
Function declared in a loop contains unsafe references to variable(s) 'lastResult', 'biologyBooks', 'page' no-loop-func
Here's the code:
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({ total: 0, biologyBooksByAuthor: [] });
let isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async() => { // fetch items
let page = 1;
let scienceBooks, biologyBooks;
// create empty arrays to store book objects for each loop
let scienceBooks = biologyBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do { // the looping - this is what I have failed to refactor
try {
await apiFullCall( // Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`
).then(res => {
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
body &&
body.results &&
body.results.map(eachBook => { // we map() over the returned "results" array
// the author with queried "author_id" writes science books;
// so we add each book (an object) into the science category
scienceBooks.push(eachBook);
// We then filter the author's biology books (from other science books)
biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof(is_biology) === "boolean" && is_biology === true
);
return null;
}
);
// increment the page with 1 on each loop
page++;
}
}
}).catch(error => console.error('Error while fetching data:', error));
} catch (err) { console.error(`Oops, something went wrong ${err}`); }
// keep running until there's no next page
} while (lastResult.next !== null);
// update the state
setState(prevState => ({
...prevState, total: scienceBooks.length, biologyBooksByAuthor: biologyBooks,
}));
};
React.useEffect(() => { // fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
};
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
}
Please note that I actually declared the said variables lastResult, biologyBooks and page outside the "do-while".
Any help or clues will be greatly appreciated.
The function the warning is referring to is the .then callback, if you're using async/await stick to it, try removing the .then part by assigning the result to a variable instead and remove the unnecessary .map, you can concatenate previous results with spread operator or .concat.
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({
total: 0,
scienceBooksByAuthor: [],
});
const isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async () => {
// fetch items
let page = 1;
let scienceBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do {
// the looping - this is what I have failed to refactor
try {
const res = await apiFullCall(
// Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`,
);
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
// concatenate new results
scienceBooks = [
...scienceBooks,
...((lastResult && lastResult.results) || []),
];
// increment the page with 1 on each loop
page += 1;
}
}
} catch (err) {
console.error(`Oops, something went wrong ${err}`);
}
// keep running until there's no next page
} while (lastResult.next !== null);
const biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof is_biology === 'boolean' && is_biology === true,
);
// update the state
setState(prevState => ({
...prevState,
total: scienceBooks.length,
scienceBooksByAuthor: scienceBooks,
}));
};
React.useEffect(() => {
// fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
}
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
};
A button calls the function signAllBrowsed, which contains two other functions:
loadSafetyLetters is a hook that makes a database call for some data and sets it in context
signAll is a hook that tries to access data in context to do something with it
Context is getting set properly, but when signAll accesses it, the data is not updated. Is there a way to access the updated context without directly passing it to the 2nd function? Or is there a way to call a callback once context is updated and accessible? Seems the updated context is only available after a re-render.
The component containing signAllBrowsed and the 2 hooks are siblings.
code in above image:
setModalVisible(true)
const logHeader = 'SafetyLetterHome::SignAllBrowsed'
try {
const response = await loadSafetyLetters(false) // before beginning sign all, get a fresh list of letters from db
if (Configs.SHOW_REQUEST_LOGS) console.log(`${logHeader} response`, response)
if (response === 'no api error') {
await signAll()
navigation.navigate('SafetyLetterSign')
}
} catch (error) {
const errorMessage = error.status && error.status.message ? error.status.message : error
Alert.alert('Database Error', errorMessage)
console.log(`${logHeader}`, errorMessage)
}
}
loadSafetyLetters calls the loadLetters hook:
const [getLetters] = useGetLetters()
const [sortLetters] = useSortLetters()
const [hasAPIError] = useHasAPIError()
const navigation = useNavigation()
const { setModalVisible, setShowSignAll, setSortedLetters, setUnsortedLetters } = useContext(SafetyContext)
const loadLetters = async (sort = true) => {
try {
const response = await getLetters()
const logHeader = 'SafetyHome::loadLetters'
const errorMessage = 'The following error occurred when trying to load letters:'
if (Configs.SHOW_REQUEST_LOGS) console.log(`${logHeader} response`, response)
const error = hasAPIError(response, logHeader, errorMessage)
if (error) return error
const { data } = response.data.payload
let unsortedLetters = []
if (data !== null && data.length > 0) {
data.map((item) => {
// grab only unsigned letters
if (
item.assignmentStatus === SafetySources.PENDING ||
item.assignmentStatus === SafetySources.BROWSED ||
item.assignmentStatus === SafetySources.QUESTIONS_COMPLETE
) {
unsortedLetters.push({
safetyLetterId: item.safetyLetterId,
title: item.title,
assignmentStatus: item.assignmentStatus,
filePath: item.filePath,
embeddableToken: item.embeddableToken,
sponsorId: item.sponsorId,
letterDate: item.letterDate,
form16: item.form16Enabled === '1' ? true : false,
sponsorName: item.sponsorName,
type: item.letterType,
sortOrder: item.sortOrder, // dear doctor; sortOrder === 1
})
}
})
}
if (unsortedLetters.length > 0) {
let bletters = unsortedLetters.filter((letter) => letter.assignmentStatus === SafetySources.BROWSED || letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE)
console.log('useLoadLetters; setting fresh pull of letters in context, including ', bletters.length, ' browsed letters')
setUnsortedLetters(unsortedLetters) // set in context
setShowSignAll( // show/hide sign all button
unsortedLetters.some((letter) =>
letter.assignmentStatus === SafetySources.BROWSED ||
letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE,
))
}
if (sort) {
if (unsortedLetters.length > 0) {
let sortedLetters = sortLetters(unsortedLetters) // sort letters with hook
setSortedLetters(sortedLetters) // set in context
}
}
} catch (error) {
console.log('SafetyHome::loadLetters ', error)
const errorMessage = error.status && error.status.message ? error.status.message : error
Alert.alert(
'Error Loading Letters',
`A database error has occurred. Please try again. (${errorMessage})`,
)
navigation.navigate('Home')
} finally {
setModalVisible(false)
}
}
return [loadLetters]
}
signAll hook:
const { state: { unsortedLetters },
setF16Browsed,
setQcAndBrowsed,
setModalVisible,
setSelectedLetter
} = useContext(SafetyContext)
const signAll = async () => {
let qcAndBrowsed = [] // set letter groups in context
let f16Browsed = []
unsortedLetters.forEach((letter) => {
if (
letter.assignmentStatus === SafetySources.BROWSED ||
letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE
) {
if (
letter.form16 &&
letter.assignmentStatus !== SafetySources.QUESTIONS_COMPLETE
) {
f16Browsed.push(letter)
} else {
qcAndBrowsed.push(letter)
}
}
})
setQcAndBrowsed(qcAndBrowsed)
setF16Browsed(f16Browsed)
// begin sign all with first f16 letter
if (f16Browsed.length > 0) {
setSelectedLetter(f16Browsed[0])
} else {
setSelectedLetter(null) // clear any previous viewed letter
}
setModalVisible(false)
}
return [signAll]
}
on my React Redux app, I am receiving the raw data from the API on my action creator, which I then manipulate further before dispatching the action with the refined data to Reducer. However, when running, the code seems to never make it down to point at dispatches the action to reducer, since I try to log texts and it never comes up on the console. What am I missing? I appreciate any help. Relevant action creators code below...
export const getTrainerAvailability = (trainerId) => {
console.log('getting here', trainerId)
return async (dispatch) => {
dispatch(getTrainerAvailabilityRequest());
const token = await AsyncStorage.getItem('token');
fetch(url + 'gettraineravailability/' + trainerId, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `${token}`
}
})
.then(res => res.json())
.then(res => {
console.log('RES', res) // <-- logs as expected
if (res.error) {
console.log('error')
throw(res.error);
}
dispatch(getTrainerAvailabilitySuccess(res));
})
.catch(error => {
dispatch(getTrainerAvailabilityError(error));
})
}
};
export const getTrainerAvailabilitySuccess = (data) => {
console.log('RAW DATA: ', data[0]) // <-- this is logged as expected
const rawAvailability = data[0]
let mondayAvailability = [];
let tuesdayAvailability = [];
let wednesdayAvailability = [];
let thursdayAvailability = [];
let fridayAvailability = [];
let saturdayAvailability = [];
let sundayAvailability = [];
for (let item of rawAvailability) {
if (item.week_day === 'Monday') {
mondayAvailability.push(item);
}
else if (item.week_day === 'Tuesday') {
tuesdayAvailability.push(item);
}
else if (item.week_day === 'Wednesday') {
wednesdayAvailability.push(item);
}
else if (item.week_day === 'Thursday') {
thursdayAvailability.push(item);
}
else if (item.week_day === 'Friday') {
fridayAvailability.push(item);
}
else if (item.week_day === 'Saturday') {
saturdayAvailability.push(item);
}
else if (item.week_day === 'Sunday') {
sundayAvailability.push(item);
}
}
console.log('im here', mondayAvailability) // this is not logged
const interval = moment.duration(30, 'minutes');
let startTime;
let endTime
let arrayLength;
let slotTime;
let refinedData;
// further array manipulation...
console.log('REFINED DATA: ', refinedData) // <- this is not logged
return {
type: 'GET_TRAINER_AVAILABILITY_SUCCESS',
payload: refinedData
}
};
This isn't really an answer, but I can't post it easily in the comments.
1) Add some logging middleware so you can see what's going on. Mine is this:
const logger = store => next => action => {
//LOTS OF LOGGING
//console.log('[DISPATCHING] %s', action.type, JSON.stringify(action) )
//SOME LOGGING
console.log('[DISPATCHING] %s', action.type)
const result = next(action)
console.log('[NEXT STATE]', store.getState())
return result
}
2) If you're using a set of asynchronous actions (waiting/retrying a fetch for instance) you might want to check out redux-saga. It was basically built to deal with asynchronous workflows.
3) This isn't a complete example so I can't give you any feedback on other problem points. there are missing methods getTrainerAvailabilityRequest & getTrainerAvailabilityError for example
4) Add more logging, it is the best way to debug this if you can't attach a debugger. Also, are you using a debugger? Chrome development tools work quite well if you build with source maps. If not, drop in a lot of log-statements. You'll figure out what's hanging. There's also a really good chance there's an error in your success method. I would definitely put a logging line in that for loop to make sure it is actually running and exiting. You're processing data from a fetch call, that is always a failure point.
And onto the less opinionated stuff...
The problem point I see here is:
for (let item of rawAvailability) { What is rawAvailability? Is it actually an array?
Basically I'd try this:
for (let item of rawAvailability) {
console.log(`[DEBUG]`, JSON.stringify(item, null, 2) );
if (item.week_day === 'Monday') {
mondayAvailability.push(item);
}
else if (item.week_day === 'Tuesday') {
tuesdayAvailability.push(item);
}
else if (item.week_day === 'Wednesday') {
wednesdayAvailability.push(item);
}
else if (item.week_day === 'Thursday') {
thursdayAvailability.push(item);
}
else if (item.week_day === 'Friday') {
fridayAvailability.push(item);
}
else if (item.week_day === 'Saturday') {
saturdayAvailability.push(item);
}
else if (item.week_day === 'Sunday') {
sundayAvailability.push(item);
} else {
console.log(`[INVALID_ITEM]`, JSON.stringify(item, null, 2) );
}
}
Depending on your setup an error thrown in redux may not show up. Adding logging so you can see all your actions and data as well as logging inside possible failure points should show you the problem.
Good luck! This isn't really an answer, it is just how I would approach it.
I have a recipe details screen and I want to add an interstitial ad every time the user enters to see the recipe details, but I want to limit it to be shown every three times, because when the user exits and re-enters it, a recipe or another recipe shows the ad again and I don't want this.
How can I do this?
import { AdMobInterstitial, setTestDeviceIDAsync } from 'expo-ads-admob';
export default class RecipeDetails extends Component {
initAds = async () => {
const INTERSTITIAL_ID = Platform.OS == "ios" ? ConfigApp.IOS_INTERSTITIAL_ID : ConfigApp.ANDROID_INTERSTITIAL_ID;
AdMobInterstitial.setAdUnitID(INTERSTITIAL_ID);
await setTestDeviceIDAsync(ConfigApp.TESTDEVICE_ID);
await AdMobInterstitial.requestAdAsync({ servePersonalizedAds: true});
await AdMobInterstitial.showAdAsync();
};
componentDidMount() {
this.initAds();
}
render() {
return (
<View>
// content
</View>
);
}
I think you can use async-storage to store times every time when you called ads. And read every times you have stored to compare if it equals three before playing ads?
Store data(times)
storeData = async () => {
try {
await AsyncStorage.setItem('#storage_Key', 'stored value')//
} catch (e) {
// saving error
}
}
Read data(times)
getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_Key')
if(value !== null) {
// value previously stored
}
} catch(e) {
// error reading value
}
}
--------------update----------------Something you want like this-------
initAds = async () => {
const INTERSTITIAL_ID = Platform.OS == "ios" ? ConfigApp.IOS_INTERSTITIAL_ID : ConfigApp.ANDROID_INTERSTITIAL_ID;
try{
const value = await AsyncStorage.getItem('play_ad_times')
if(value !== null) {
if(value == "3"){
await AsyncStorage.setItem('play_ad_times', "1"); //If three times back to one times and play once
AdMobInterstitial.setAdUnitID(INTERSTITIAL_ID);
await setTestDeviceIDAsync(ConfigApp.TESTDEVICE_ID);
await AdMobInterstitial.requestAdAsync({ servePersonalizedAds: true});
await AdMobInterstitial.showAdAsync();
}else{
var temp = parseInt(value)+1;
await AsyncStorage.setItem('play_ad_times', temp.toString() );
}
// value previously stored
}else{
//first time in
await AsyncStorage.setItem('play_ad_times', "1"); //Set time 1
AdMobInterstitial.setAdUnitID(INTERSTITIAL_ID);
await setTestDeviceIDAsync(ConfigApp.TESTDEVICE_ID);
await AdMobInterstitial.requestAdAsync({ servePersonalizedAds: true});
await AdMobInterstitial.showAdAsync();
}
}catch(e) {
// error reading value
await AsyncStorage.setItem('play_ad_times', "1");
}
};