AsyncStorage doesn't save data but no error - reactjs

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

Related

React native: how do I wait for a state to be set, before I call another state related operation?

I am writing a chat app. Users can search for other users, and then press the "Message" button. Then I navigate to ChatScreen.js. If both users have been messaging each other, I set the chatId variable accordingly. If they have not messaged each other before I dont create chatId, until the ery first message has been sent. When the first message is sent, I first, create new chat, store its properties (user ids, chatId, etc) in my db and then I sent the first message. The problem is that I store chatId as a state variable, and when I create the chat I call setChatId(id). setChatId() is not synchronous call, so by the time when I need to send message with sendText(text, chatId); my chatId is undefined even though I have already created a chat and I have called setChatId.
How can I avoid this error? Ofc, I can check if chatId == undefined then calling sendText(text, id), otherwise calling sendText(text, chatId). Is there a better/neath way to avoid the undefined check?
Here is part of my code:
...
import {
createChat,
} from "./actions";
...
function ChatScreen(props) {
...
const [chatId, setChatId] = useState(props.route.params.chatId);
...
const setupChat = async () => {
try {
await createChat(user.id, setChatId);
props.fetchUserChats();
} catch (error) {
console.error("Error creating chat: ", error);
}
};
async function handleSend(messages) {
if (!chatId) {
// creating chat
await setupChat();
}
const text = messages[0].text ? messages[0].text : null;
const imageUrl = messages[0].image ? messages[0].image : null;
const videoUrl = messages[0].video ? messages[0].video : null;
const location = messages[0].location ? messages[0].location : null;
//assuming chatId is already setup but it is not
if (imageUrl) {
sendImage(imageUrl, chatId, setSendImageError);
} else if (location) {
sendLocation(location, chatId, setLocationError);
} else if (videoUrl) {
sendVideo(videoUrl, chatId, setSendImageError);
} else {
sendText(text, chatId);
}
}
...
}
My createChat function from actions.js file
export async function createChat(otherUid, setChatId) {
let chatId = firebase.auth().currentUser.uid + "_" + otherUid;
await firebase
.firestore()
.collection("Chats")
.doc(chatId)
.set({
users: [firebase.auth().currentUser.uid, otherUid],
lastMessage: "Send the first message",
lastMessageTimestamp: firebase.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log("doc ref for creatign new chat: ", chatId);
setChatId(chatId);
})
.catch((error) => {
console.error("Error creating chat: ", error);
});
}
Instead of using a state variable, I would advise you to use useRef(). This would be a good solution to your problem.Eg Define it this way
const chatId = useRef(null),
then set it this way chatId.current = yourChatId
and get it this way chatId.current. I hope this solves your problem

Update a value before to request into API

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.

How can I show ad every three times when entering a screen in React Native?

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");
}
};

How to show success message if data is fetched successfully in console.log?

I am using stripe library in react-native, I am getting data in console.log successfully, but i want to redirect to another page if it's success, or if it has any error then show on page. I've tried to redirect to another page but it's redirecting even it has an error or not, I don't know how to do this. I've tried to do it like this..
Here is my code.
payme() {
const apiKey = '<api_key>';
const client = new Stripe(apiKey);
client.createToken({
number: this.state.number,
exp_month: this.state.expmonth,
exp_year: this.state.expyear,
cvc: this.state.cvc,
}).then((x) => {
let successmsg = x;
NavigationService.navigate('LoginPage');
}).catch((e) => {
console.log(e);
})
}
render() {
return (
<Text>{this.successmsg}</Text>
)
}
Can anyone help me about how can i show the error and how can i redirect to other page only on success.
based on your code you will get error in this.successmsg
state = {
successmsg : null
}
async payme() {
try{
const apiKey = '<api_key>';
await result = new Stripe(apiKey).createToken({
number: this.state.number,
exp_month: this.state.expmonth,
exp_year: this.state.expyear,
cvc: this.state.cvc,
})
if(!result.error){
//do your success logic
//set your successmsg based on reslut object option
this.setState({successmsg : "YOURCUSTOM MESSAGE FROM RESULT OBJECT"})
NavigationService.navigate('LoginPage');
} else {
// throw your error here
throw "something went wrong"
}
}
catch(e){
throw new Error(e);
}
}
render() {
return (
<Text>{this.successmsg}</Text>
)
}

Prevent Facebook login from remounting the app

There seems to be an issue with my implementation of the Facebook login for react-native
When I click on "Log in with the Facebook app" it opens the facebook app, then when I click "Continue" it reopens my app but it performs a remount. Part of my logic is to open another modal after that for some extra information from the user but calling this.setState() in the callback from the facebook login gives a `Can't call setState (or forceUpdate) on an unmounted component. And I can't seem to figure out how to get around this.
facebookLogin = async () => {
let result;
try {
this.setState({
loggingInWithFacebook: true
})
LoginManager.setLoginBehavior('native');
result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
} catch (nativeError) {
this.setState({
loggingInWithFacebook: false
})
this.props.dispatch(openSnackBar({
message: "There was an error opening facebook to login"
}))
}
if (result.isCancelled) {
this.setState({
loggingInWithFacebook: false
})
} else {
this.FBGraphRequest('first_name, last_name, id, email', this.FBLoginCallback);
}
}
FBGraphRequest = async (fields, callback) => {
const accessData = await AccessToken.getCurrentAccessToken();
const infoRequest = new GraphRequest('/me', {
accessToken: accessData.accessToken,
parameters: {
fields: {
string: fields
}
}
}, callback);
new GraphRequestManager().addRequest(infoRequest).start();
}
FBLoginCallback = async (error, result) => {
if (error) {
this.setState({
loggingInWithFacebook: false
});
} else {
const {id, picture, ...rest} = result
this._handleSocialLogin({...rest, facebook: id})
}
}
_handleSocialLogin = (data) => {
this.props.dispatch(socialLogin(data)).then(res => {
this.props.dispatch(getGroups()).then(res => {
this.setState({
loggingInWithFacebook: false,
loggingInWithGoogle: false
})
AsyncStorage.getItem('onBoarded').then((res)=> {
if (res === 'true') {
this.props.navigation.navigate("Map")
} else {
this.props.navigation.navigate("OnBoarding")
}
})
}).catch(err => console.log(err.response.body))
}).catch(err => {
if (err.err === 'not found') {
this.setState({
enterPhoneModalVisible: true,
socialData: data
})
} else {
this.props.dispatch(
openSnackBar({
message: "There was an error logging in, please try again"
})
);
this.setState({
loggingInWithFacebook: false,
loggingInWithGoogle: false
})
}
})
}
EDIT:
My Appdelegate looks like this
#import <FBSDKCoreKit/FBSDKCoreKit.h>
In my didFinishLaunchingWithOptions
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
BOOL handledFB = [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
];
BOOL handledRCT = [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
return handledFB || handledRCT;
EDIT 2:
I seem to have gotten it to work by changing my openUrl to the following
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
] || [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
Any idea why this works?
EDIT 3:
I'm still getting a Login Failed error randomly and there doesn't seem to be any definitive way to cause this to happen it just happens sometimes and then trying to login again right after it usually goes through

Resources