How to use asyncStorage inside useEffect - react-native

I'm building a mobile game using react native and I'm trying to retrieve the best value storage on it to display on the screen. The problem is that it seems that react native is rendering the screen before it retrieves the value and then it doesn't re-render when the value is updated using setBest(), so no value is displayed.
Here is the code:
const navigation = useNavigation()
const [result, setResult] = useState('')
const [best, setBest] = useState('')
useEffect(() => {
const Storage = async (key,value) => {
await AsyncStorage.setItem(key,value)
}
const Retrieve = async (key) => {
const value = await AsyncStorage.getItem(key)
setBest(()=>value)
}
Retrieve('1').catch(console.error)
setResult(route.params.paramKey)
if(route.params.paramKey>best){
var aux = result.toString()
Storage('1',aux)
console.log(best)
}
}, [])
return (
<View style={styles.container}>
<View style={styles.textView}>
<Text style={styles.tituloText}>Melhor pontuação</Text>
<Text style={styles.tituloText}>{best}</Text>
<Text style={styles.tituloText}>Sua pontuação</Text>
<Text style={styles.resultText}>{result}</Text>
<View style={styles.viewBtn}>
<TouchableOpacity style={styles.viewBack} onPress={() => navigation.navigate('Modo1')}>
<Icon style={styles.iconBack} name="backward" />
</TouchableOpacity>
<TouchableOpacity style={styles.viewHome} onPress={() => navigation.dispatch(StackActions.popToTop)}>
<Icon style={styles.iconBack} name="home" />
</TouchableOpacity>
</View>
</View>
</View>
);
}
Thanks for the help guys! I've been struggling with this for days and any help will be appreciated!

This is how you retrieve the value..
useEffect(() => {
AsyncStorage.getItem('key').then(value => {
if (value != null) {
console.log(value);
setBest(value);
}
});
}, []);
also don't forget to add the import statement..
To set the value you must use
AsyncStorage.setItem('key', value);

You can use Async Functions inside of ~useEffect()` like this:
useEffect(() => {
(async () => {
async function getData() {
try {
const value = await AsyncStorage.getItem('myKey');
if (value !== null) {
setData(value);
}
} catch (error) {
console.log(error);
}
}
getData();
})();
}, []);
}

Related

Is it possible to render/return React Native elements from functions?

so i want to load some data from my server using axios in React native. The data was retrieved successfully, but i don't know how to display it on the page. When i click button 'Load students' it does axios get method and after that calls method 'showStudents' but that method doesn't return anything. I really don't understand how rendering works in react native so i would appreciate any help and guidance. Also if there is easier way to do all of this, i'm open for suggestions.
export default function Students() {
const [s, setStudents] = useState('')
const getStudents = async () => {
try{
const {data: {students}} = await axios.get('http://192.168.1.2:3000/api/v1/students')
setStudents(students)
//console.log(students)
showStudents()
}
catch(error){
console.log(error)
}
}
const showStudents = () => {
return( <ScrollView>
{
s.map((student) => (
<ListItem key={student._id} bottomDivider>
<ListItem.Content>
<ListItem.Title>{student.firstName}</ListItem.Title>
<ListItem.Subtitle>{student.index}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView>)
}
return (
<View style={styles.container}>
<Button title='Load students' color='green' onPress={getStudents}/>
</View>
);
}
The function showStudents returns a JSX component, but not inside of the render function of the component Students.
You can just create a new JSX component and use conditional rendering in order to render it whenever the state s (I would call it students) is not undefined and has a length strictly greater than zero.
const [students, setStudents] = useState()
const getStudents = async () => {
try{
const {data: {students}} = await axios.get('http://192.168.1.2:3000/api/v1/students')
setStudents(students)
}
catch(error){
console.log(error)
}
}
return (
<View style={styles.container}>
<Button title='Load students' color='green' onPress={getStudents}/>
{
students && students.length > 0 ? <ScrollView>
{
students.map((student) => (
<ListItem key={student._id} bottomDivider>
<ListItem.Content>
<ListItem.Title>{student.firstName}</ListItem.Title>
<ListItem.Subtitle>{student.index}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView> : null
}
</View>
);
We could create a new component to make things more structured. Let us introduce StudentList.
export function StudentList({students}) {
return <ScrollView>
{
students.map((student) => (
<ListItem key={student._id} bottomDivider>
<ListItem.Content>
<ListItem.Title>{student.firstName}</ListItem.Title>
<ListItem.Subtitle>{student.index}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView>
}
Then, reuse this new component.
const [students, setStudents] = useState()
const getStudents = async () => {
try{
const {data: {students}} = await axios.get('http://192.168.1.2:3000/api/v1/students')
setStudents(students)
}
catch(error){
console.log(error)
}
}
return (
<View style={styles.container}>
<Button title='Load students' color='green' onPress={getStudents}/>
{
students && students.length > 0 ? <StudentList students={students} /> : null
}
</View>
);

Counter with Async Storage in React Native

I am new to React Native.
I want to make a counter using Async storage in React Native Expo.
Async storage works with string value but I need to use integer value and can't find an example to create it.
I would appreciate it if you suggest with SQLite or if there is a different storage area.
storeData = async (counter) => {
try {
await AsyncStorage.setItem('', counter)
} catch (e) {
}
}
getData = async () => {
try {
const value = await AsyncStorage.getItem('counter')
if(counter !== null) {
}
} catch(e) {
}
}
render() {
return(
<SafeAreaView style={styles.container}>
<ImageBackground style={styles.image}>
<View style={{marginBottom: 250}}>
<Text style={styles.counter}>{counter}</Text>
</View>
<TouchableOpacity
style={styles.floatingButton1}
onPress={this.onAddCounter}>
<Text style={{fontSize:13, color:"white", fontWeight:"600"}}>Tap to Counter</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.resetButton1}
onPress={this.onReset1}>
<Icon name="undo" size={20} color="#900"/>
</TouchableOpacity>
</ImageBackground>
</SafeAreaView>
);
}
}
You can convert the integer to a string when you store the value:
number.toString()
And convert it to integer when you retrieve the value
parseInt(string)
Basically it will become
storeData = async (counter) => {
try {
await AsyncStorage.setItem('counter', counter.toString())
} catch (e) {
}
}
getData = async () => {
try {
const value = await AsyncStorage.getItem('counter')
if(counter !== null) {
value = parseInt(value)
}
} catch(e) {
}
}
Use JSON.parse for values getting from AsyncStorage
https://react-native-async-storage.github.io/async-storage/docs/usage/#reading-object-value

React-Native FlatList item clickable with data to another screen

I'm trying to access a screen when you click on an item in my flatlist by passing the date I retrieved from the firebase before, I've tried several things without success so I come to you.
Basically when I click on one of the elements -> A screen with details should appear.
export default function Notifications() {
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => { * HERE I NEED TO PASS DATA AND SHOW AN ANOTHER SCREEN FOR DETAILS * }}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
EDIT: Small precision this screen is located in a Tab.Navigator
you can pass params in navigation,
export default function Notifications(props) {
const { navigation } = props
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('screenName', {
//pass params here
})
}}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
you can access params in the navigated screen by props.route.params

How do I update state inside function in react native?

I have a simple functional component, where depending on the state I show "add item" or "remove item" button:
const SimpleComponent = ({ route }) => {
const { id } = route.params;
const [add, setAdd] = useState(false);
useEffect(() => {
{ isCurrentUserHavingThisItem(route, id) ?
setAdd(true)
:
setAdd(false)
}
}, [])
return (
<View >
{ add ?
(
<TouchableOpacity onPress={() => removeItem(route, id)}>
<Text> Remove this item </Text>
</TouchableOpacity>
)
:(
<TouchableOpacity onPress={() => addItem(route, id)}>
<Text> Add item </Text>
</TouchableOpacity>
)
}
</View>
);
};
const isCurrentUserHavingThisItem = (route, id) => {
firebase.firestore()
.collection('Users')
.doc(id)
.collection('Items')
.doc(route.params.item.key)
.get()
.then((docSnapshot) => {
console.log("my return statement: ", docSnapshot.exists);
setAttending(docSnapshot.exists);
return docSnapshot.exists;
})
.catch((error) => {
console.log(error);
return false;
});
}
I think that isCurrentUserHavingThisItem is working fine. However, when I press the "Add item" I trigger the following function:
const addItem = (route, id) => {
firebase.firestore()
.collection('Users')
.doc(id)
.collection('Items')
.doc(route.params.item.key)
.set({})
.then(setAdd(true));
}
Which gives me the following error: ReferenceError: Can't find variable: setAdd I also tried the following:
const addItem = (route, id) => {
firebase.firestore()
.collection('Users')
.doc(id)
.collection('Items')
.doc(route.params.item.key)
.set({});
setAdd(true);
}
but it gave me the same error. Here is the whole component structure for better visibility:
const SimpleComponent = ({ route }) => {
const { id } = route.params;
const [add, setAdd] = useState(false);
useEffect(() => {
{ isCurrentUserHavingThisItem(route, id) ?
setAdd(false)
:
setAdd(true)
}
}, [])
return (
<View>
{ add ?
(
<TouchableOpacity onPress={() => removeItem(route, id)}>
<Text style={styles.title}>Remove item</Text>
</TouchableOpacity>
)
:(
<TouchableOpacity onPress={() => addItem(route, id)}>
<Text style={styles.title}>Add item</Text>
</TouchableOpacity>
)
}
</View>
);
};
const isCurrentUserHavingThisItem = (route, id) => {
//same as I described it above; I think it works fine
}
const addItem = (route, id) => {
//same as described above
//gives me error when I try to update the state
}
const leaveMeetup = (route, id) => {
//same as described above
//gives me error when I try to update the state
}
export default SimpleComponent;
Basically, after I add the item successfully, I want to display "remove item" button and vice versa. In order to do so I try to update the state but as I said I have ReferenceError: Can't find variable: setAdd error and I dont see why. Can someone help?
Your setAdd method is outside the component that's why it is showing that error...Please move your methods inside your functional component like this.
const SimpleComponent = ({ route }) => {
const { id } = route.params;
const [add, setAdd] = useState(false);
const isCurrentUserHavingThisItem = (route, id) => {}
const addItem = (route, id) => {}
const leaveMeetup = (route, id) => {}
useEffect(() => {
{ isCurrentUserHavingThisItem(route, id) ?
setAdd(false)
:
setAdd(true)
}
}, [])
return (
<View>
{ add ?
(
<TouchableOpacity onPress={() => removeItem(route, id)}>
<Text style={styles.title}>Remove item</Text>
</TouchableOpacity>
)
:(
<TouchableOpacity onPress={() => addItem(route, id)}>
<Text style={styles.title}>Add item</Text>
</TouchableOpacity>
)
}
</View>
);
};
export default SimpleComponent;
and i think you can't do it like this.
.then(setAdd(true));
You get a callback in .then()
So the correct way to do this is
.then(()=>{
setAdd(true)
}).

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application

I want to login to my app but when I first login it works correctly but once I logout from my app and again try to login I get the following error 'Can't perform a state update on an unmount component'. Even though second time it also enters in the app but with the error which should be not there. Only one time it works correctly.
/*Component*/
const LoginScreen = props => {
let _isMounted = false;
const [isLoading , setIsLoading] = useState(false);
const [error , setError] = useState();
const [token , setToken] = useState();
const [url , setUrl] = useState({});
const dispatch = useDispatch();
/*Receiving the token*/
useEffect(() => {
let _isMounted = false;
const tokenReceive = () => {
if(Object.entries(url).length !== 0)
{
const getTokenFromUrl = url['url'].split('=')[1].split('&')[0];
if(getTokenFromUrl !== '')
{
setToken(getTokenFromUrl)
}
}
}
tokenReceive();
return(() => {
_isMounted = true
} )
}, [url ])
/*Dispatching after receiving token*/
useEffect(() =>{
_isMounted = true;
const loginHandler = async ()=> {
if(token !== undefined)
{
setError(null)
setIsLoading(true);
try{
await dispatch(authActions.login(token))
// if(_isMounted){
// props.navigation.navigate('afterAuth')
// }
}
catch(err)
{
setError(err.message)
}
setIsLoading(false)
if(_isMounted){
props.navigation.navigate('afterAuth')
}
}
}
loginHandler()
return(() => {
_isMounted = false
} )
} , [token ])
/*If any error occur*/
useEffect(() => {
if (error) {
Alert.alert('An error occured',error,[{text : 'Okay'}]);
}
return(() => {
console.log('Error'),
error
})
} , [error])
/*Event listener when url changes*/
useEffect(() => {
Expo.Linking.addEventListener('url', (url) => {
setUrl(url);
})
return () => {
Expo.Linking.removeEventListener('url' , (url) => {
setUrl(url)
})
};
} , [])
const prefix = Expo.Linking.makeUrl('token');
const _handlePressButtonAsync = async () => {
let result = await WebBrowser.openBrowserAsync(`https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=**********&response_type=id_token&redirect_uri=${prefix}&scope=openid email profile&response_mode=fragment&state=*****&nonce=****`);
};
return(
<ScrollView >
<TouchableWithoutFeedback onPress={() => {Keyboard.dismiss()}} >
<View style={styles.screen}>
<CircleDiv style={styles.userlogoDiv}>
<View style={styles.userLogo}>
<AntDesign name="user" size={RFValue(39)} color='#4D4848'/>
</View>
</CircleDiv>
<BackgroundUpper style={styles.upperDiv}>
<LogoLong style={ {marginTop : RFValue(100)}}/>
</BackgroundUpper>
<BackgroundLower >
<ScrollView style={{ flex : 1 } } decelerationRate='fast' >
<KeyboardAvoidingView behavior='position' keyboardVerticalOffset={Dimensions.get('screen').height / RFValue(10)}>
<View style={styles.loginDiv}>
<View style={styles.headingDiv}>
<Text style={styles.heading}>LOGIN</Text>
</View>
<View style={styles.buttonDiv}>
<TouchableOpacity>
{!isLoading ? <Button
style={styles.button}
title='LOGIN'
color= '#00B49D'
//onPress = {navigate}
onPress={_handlePressButtonAsync}
/> : <ActivityIndicator size="small" color={Colors.GREEN}/>}
</TouchableOpacity>
</View>
<View style={styles.forgetDiv}>
<Text style={styles.forget}>Forget Password</Text>
</View>
</View>
</KeyboardAvoidingView>
</ScrollView>
</BackgroundLower>
</View>
</TouchableWithoutFeedback>
</ScrollView>
)
};
Error - Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function,