AsyncStorage doesn't return null at first start - react-native

I'm making a login using a JWT and deviceStorage, it's working okay but every time I start the app, and with JWT removed from deviceStorage, it start as if already logged in. The problem is that the get method in deviceStorage returns a promise, so I need to either make the get method return null if empty or have my program know if it's a string or a promise
APP.JS
import 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import React, { useState, useEffect } from 'react';
import Login from './src/Pages/Login';
import LoggedIn from './src/Pages/LoggedIn';
import deviceStorage from './src/Service/deviceStorage';
const App = () => {
const [JWT, setJWT] = useState(null);
useEffect(() => {
const checkJWT = () => {
setJWT(deviceStorage.getValue())
}
checkJWT()
}, []
);
const checkJWT = () =>{
if (!JWT || JWT === null || JWT === "") {
return <Login setJWT={setJWT} />
}else if (JWT){
return <LoggedIn JWT={JWT} setJWT={setJWT} />
}
}
return (
<SafeAreaProvider>
{checkJWT()}
</SafeAreaProvider>
)
}
export default App
DEVICESTORAGE
import AsyncStorage from '#react-native-async-storage/async-storage';
const key = 'currentUser';
const deviceStorage = {
async saveItem(value) {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem(key, jsonValue);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
console.log('Done saving Value.')
},
async getValue() {
try {
return await AsyncStorage.getItem(key)
} catch(e) {
console.log('AsyncStorage Error: ' + e.message);
}
console.log('Done getting value.')
},
async removeValue() {
try {
await AsyncStorage.removeItem(key)
} catch(e) {
console.log('AsyncStorage Error: ' + e.message);
}
console.log('Done removing Value.')
}
};
export default deviceStorage;
I hope someone can help me with this

As you pointed out, the get method of deviceStorage returns a promise currently. I think you can do one of these:
If useEffect can accept an async method I think this should work:
useEffect(async () => {
const checkJWT = () => {
setJWT(await deviceStorage.getValue())
}
checkJWT()
}, []);
If not, then something like this should work:
useEffect(() => {
const checkJWT = () => {
deviceStorage.getValue().then(value => setJWT(value));
}
checkJWT()
}, []);
I haven't checked that anywhere if that works, but something along those lines should do the trick. You should make sure that you indeed put a JWT in your setJWT method and not a Promise. Or maybe you could change your setJWT method so it knows, that if it received a promise then you have to wait for the result.

Try out this function :
async getValue() {
try {
let value = null;
value = await AsyncStorage.getItem(key);
return value;
} catch(e) {
console.log('AsyncStorage Error: ' + e.message);
}
console.log('Done getting value.')
}

Related

React Native useEffect not run when app first opened

I am trying to make an app where the USER_ID is loaded from the device's local storage if found, otherwise, a new id is generated and stored in the local storage. I am trying to make use of React useContext() to make the USER_ID visible to the whole app after it is first run.
import React, {useEffect, useState} from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
import uuid from 'react-native-uuid';
export const UserIdContext = React.createContext('undef');
export const UserIdProvider = ({children}) => {
const [userId, setUserId] = useState('');
useEffect(() => {
async function getOrInitUserId() {
try {
let temp = await AsyncStorage.getItem('USER_ID');
if (temp == null) {
temp = uuid.v4();
await AsyncStorage.setItem('USER_ID', uuid.v4());
console.log('USER_ID Generated: ' + temp);
} else {
console.log('USER_ID Found: ' + temp);
}
setUserId(temp);
} catch (error) {
console.error(error);
}
}
if (!userId) {
getOrInitUserId();
}
});
return (
<UserIdContext.Provider value={userId}>{children}</UserIdContext.Provider>
);
};
export const useUserId = () => React.useContext(UserIdContext);
The provider is used as below:
const App = () => {
useEffect(() => {
....
});
return (
<UserIdProvider>
...contents of app...
</UserIdProvider>
);
};
export default App;
However, the useEffect() of < UserIdProvider /> is not run as the app is launched for the first time after being installed on a device, as there is no log on the console. After the app is closed/quit and relaunched, the console log a USER_ID found, instead of USER_ID generated.
Add 2nd argument as [] so it will render only once. Otherwise, it will render every time when any state will be updated.
const App = () => {
useEffect(() => {
....
},[]);
return (
<UserIdProvider>
...contents of app...
</UserIdProvider>
);
};
export default App;
In order to trigger useEffect when first run you should enter [] as prop like this:
useEffect(() => {
async function getOrInitUserId() {
try {
let temp = await AsyncStorage.getItem('USER_ID');
if (temp == null) {
temp = uuid.v4();
await AsyncStorage.setItem('USER_ID', uuid.v4());
console.log('USER_ID Generated: ' + temp);
} else {
console.log('USER_ID Found: ' + temp);
}
setUserId(temp);
} catch (error) {
console.error(error);
}
}
if (!userId) {
getOrInitUserId();
}
}, []);

observable and computed not being reflected in a functional component

I am learning mobx for react-native and not able to see changes to done to observables or computed.
Basically, I want to listen to changes to observable from the component.
My store is simple:
import { observable, action, computed } from 'mobx';
import AsyncStorage from '#react-native-async-storage/async-storage';
class ConfigStore {
rootStore = undefined;
#observable activeConfig = {group: 'starter', TC: false};
constructor(rootStore) {
this.rootStore = rootStore;
}
#computed get termsLoaded(){
return this.activeConfig.TC;
}
#action async loadPreviousConfig() {
const configDetails = { group: 'starter', TC: false};
try {
const response = await AsyncStorage.multiGet([
'group',
'TC'
]);
configDetails.group = response[0][1] || 'starter';
configDetails.TC = response[1][1] === undefined ? false : true;
console.log(configDetails);// shows correct previously saved config
this.activeConfig = configDetails;
} catch (error) {}
}
}
export default ConfigStore;
From my component, I want to load first previous configuration settings and have them reflect in my app. Basically, I want to check the value of TC ater calling loadPreviousConfig, they return false still:
import { inject, observer } from 'mobx-react';
const ConfigComponent = (props) => {
const { store } = props;
const { termsLoaded, activeConfig, loadPreviousConfig } = store.configStore;
useEffect(() => {
const init = async () => {
await loadPreviousConfig();
console.log(termsLoaded); //always false even though console from the store shows it is true.
};
init();
}, []); //tried [props]
return (
<View>
<Text>{activeConfig.group}</Text> //never changes
</View>
);
};
export default inject('store')(observer(ConfigComponent));

React Native - How to update a value for entire application

What is the best way to find a value in the api request and make it available in the entire application? I need to get the total of unread messages and show it to the user in the application header. Every time you change pages, a new request is made to update the total of unread messages. I'm using a context like this:
/** #format */
import React, { createContext, useContext, useState, useEffect, useRef } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
import api from "../services/api";
const BadgeContext = createContext();
export default function BadgeProvider({ children }) {
const [messageCount, setMessageCount] = useState(0);
const [userLogged, setUserLogged] = useState("");
async function getAuthUserFromStorage() {
try {
const dataFromStorage = await AsyncStorage.getItem("#ellot:authUserLogged");
const authUserLogged = JSON.parse(dataFromStorage);
setUserLogged(authUserLogged.user);
} catch (e) {
console.log("ERROR: ", e);
}
}
useEffect(() => {
getAuthUserFromStorage();
}, []);
useEffect(() => {
const getTotalMessageNotRead = async () => {
try {
const response = await api.get(`/messages/total-not-read/${userLogged.id}`);
setMessageCount(response.data.data);
console.log("messageCount context ", messageCount);
} catch (error) {
console.log("Message Not Read Error: ", error);
} finally {
console.log("finally message not reader context", messageCount);
}
};
getTotalMessageNotRead();
}, []);
async function resetCountMessage() {
setMessageCount(0);
}
const store = {
messageCount,
resetCountMessage,
};
return <BadgeContext.Provider value={store}>{children}</BadgeContext.Provider>;
}
export function useBadge() {
const context = useContext(BadgeContext);
const { messageCount, resetCountMessage } = context;
return { messageCount, resetCountMessage };
}

Whats the correct way to invoke a registered task?

I am using expo version 41.0.0 and i defined task and export the registered task. This is how
the code looks like
import * as BackgroundFetch from "expo-background-fetch";
import * as TaskManager from "expo-task-manager";
import axios from 'axios';
const TASK_NAME = "BACKGROUND_TASK";
TaskManager.defineTask(TASK_NAME, () => {
try {
// fetch data here...
const receivedNewData = "Simulated fetch " + Math.random()
console.log("My task ", receivedNewData)
return receivedNewData
? BackgroundFetch.Result.NewData
: BackgroundFetch.Result.NoData
} catch (err) {
return BackgroundFetch.Result.Failed
}
});
export const RegisterBackgroundTask = async () => {
try {
await BackgroundFetch.registerTaskAsync(TASK_NAME, {
minimumInterval: 5, // seconds,
});
console.log("Task registered")
} catch (err) {
console.log("Task Register failed:", err)
}
}
and this is how I invoke the registered task
import { RegisterBackgroundTask } from '../../helper/background-task';
const SomeComponent = ({ ...props }) => {
RegisterBackgroundTask();
...rest of the code
return (
<View>
other stuff here...
</View>
)
}
PS: I run the app through Expo and I am using IOS
Is this the correct way to invoke registered task, or can it be called inside a lifecycle method like useEffect ?

How to use async/await to retrieve value from AsyncStorage in react native by using function

I have created prefsManager.js - Use for storing and retrieve data from AsyncStorage but I have faced a problem like when print log it return always undefined because of it is Async but I want to print the actual value in a log by the call of the function.
import { AsyncStorage } from 'react-native';
import prefskey from '../utils/constants/prefskeys';
const setValue = async (key, value) => {
await AsyncStorage.setItem(key, value);
}
const getValue = async (key) => {
let value = '';
try {
value = await AsyncStorage.getItem(key) || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return value;
};
const prefsmanager = {
setValue,
getValue
}
export default prefsmanager;
I have used this in my Home.js when button press I'm calling this method.
_handlePress() {
await prefsManager.setValue(prefskey.username, this.state.username)
console.log("username =>>", await prefsManager.getValue(prefskey.username));
}
You need to use async keyword on your function like this.
import { AsyncStorage } from 'react-native';
import prefskey from '../utils/constants/prefskeys';
const prefsnamager = {
setValue: function (key, value) {
AsyncStorage.setItem(key, value)
},
getValue: async (key) => {
let value = '';
try {
value = await AsyncStorage.getItem(key) || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return value;
}
}
export default prefsnamager;
calling function
_handlePress = () => {
prefsManager.setValue(prefskey.username, this.state.username)
console.log("username =>>" , prefsManager.getValue(prefskey.username));
}
Set value in storage
AsyncStorage.setItem('data','Read Data')
Get value from storage
constructor(props) {
super(props)
this.state = {};
let self=this;
//this function is called everytime , when you visit this screen.
this.__didFocusSubscription = this.props.navigation.addListener('didFocus',payload => {
AsyncStorage.getItem('data').then((value)=>{
if(value==null){
self.setState({count:'no data found'})
}
else{
self.setState({count:value})
}
})
});
}
Actually, it is like localStorage in the web but with a little difference. in getting the item it acts asynchronously. pay attention to below:
AsyncStorage.setItem('key', value);
But in getting it is like below:
AsyncStorage.getItem('key')
.then( value => console.log(value) );