How do I send a function parameter to AsyncStorage? - javascript

I want to send the parameter to the function submitLanguageSelection, which is userSelectedLanguage, to a custom hook I've written which (hopefully) saves that parameter to AsyncStorage. The user selects a language, either English or Arabic, from one of the two buttons.
This is my first time ever doing this. I've gotten very stuck.
I would like the submitLanguageSelection function to call the saveData function which is made available through the useLocalStorage hook. I would like the user's choice of language to be persisted in AsyncStorage so I can then later render the ChooseYourLanguageScreen according to whether the user has selected a language or not.
Here is the cutom hook, useLocalStorage:
import React from 'react';
import { Alert } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
const STORAGE_KEY = '#has_stored_value';
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async () => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, storedValue);
if (localValue !== null) {
setStoredValue(storedValue);
Alert.alert('Data successfully saved');
}
console.log('stored val', storedValue);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};
Here is the ChooseYourLanguageScreen:
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import useLocalStorage from '../hooks/useLocalStorage';
const ChooseYourLanguageScreen = ({ navigation }) => {
const [saveData, errorMessage] = useLocalStorage();
const submitLanguageSelection = (userSelectedLanguage) => {
//TODO: save the data locally
//TODO: navigate to welcome screen
// at the moment, the language choice isn't making it to useLocalStorage
if (userSelectedLanguage !== null) {
console.log('user selected lang', userSelectedLanguage);
saveData(userSelectedLanguage);
}
};
return (
<View style={styles.container}>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text style={styles.text}>This is the Choose Your Language Screen</Text>
<View style={styles.buttons}>
<View>
<Button
title={'English'}
onPress={() => submitLanguageSelection('English')}
/>
</View>
<View>
<Button
title={'Arabic'}
onPress={() => submitLanguageSelection('Arabic')}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
alignSelf: 'center',
},
buttons: {
backgroundColor: '#DDDDDD',
padding: 10,
},
});
export default ChooseYourLanguageScreen;

saveData() needs a parameter. You can provide a default value that uses storedValue that came from React.useState(), but when you call it with an explicit argument it will override that default.
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async (dataToSave = storedValue) => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, dataToSave);
if (localValue !== null) {
setStoredValue(dataToSave);
Alert.alert('Data successfully saved');
}
console.log('stored val', dataToSave);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};

Related

Expo SecureStore not saving correctly (React native, typescript)

I'm working on a mobile phone application with Stripe and Expo Bar Code Scanner. When you start the application, if you gave the permissions for using the camera, you will can scan bar codes. Bar Codes only contains the id of the scanned item. If it exists, two buttons (+/-) will appear in order to choose the amount for the item. If it doesn't exists, nothing happens. When the amount changes, I save in SecureStore the id of the item as the key and the amount as the value.
The problem is when I move on others screens (with React Navigation) and I came back to scan and I rescan the same item, the amount resets to 0. If you don't give the permissions for the camera, it displays a list of available items when you can choose the amount (+/-) buttons and similar problem.
Here the concerned two files :
ItemListComponent.tsx
import { Button, FlatList, View, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { useState } from 'react';
export const ItemComponent = (props: any) => {
const [amount, setAmount] = useState<number>(0);
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
const save = async () => {
await SecureStore.setItemAsync(props.item.id.toString(), amount.toString());
}
return (
<View>
<Text>{props.item.name}</Text>
<Button
onPress={() => {
setAmount(amount + 1);
save();
}}
title='+'
/>
{amount > 0 &&
<Button
onPress={() => {
setAmount(amount - 1);
save();
}}
title='-'
/>
}
</View>
);
};
export const ItemListComponent = (props: any) => {
return (
<FlatList
data={props.items}
renderItem={({ item }) =>
<ItemComponent key={item.id} item={item} />
}
/>
);
};
BarCodeScannerComponent.tsx
import { BarCodeScanner } from 'expo-barcode-scanner';
import { useState } from 'react';
import { StyleSheet } from 'react-native';
import { ItemComponent } from './ItemListComponent';
import Items from '../models/ItemsModel';
export const BarCodeScannerComponent = () => {
const [item, setItem] = useState<Items>();
const getItem = async ({ data }: any) => {
const response = await fetch(`http://192.168.1.81:8000/items/${data}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const json = await response.json();
setItem(json);
}
}
return (
<View style={styles.container}>
<BarCodeScanner
onBarCodeScanned={getItem}
style={StyleSheet.absoluteFillObject}
/>
{(item !== null && item !== undefined) && <ItemComponent key={item.id} item={item} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
});
Thanks for help !
It looks like you never call getAmount, and if you did call it you'd get infinite recursion.
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
should be
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
}
getAmount();
or, probably even better:
const getAmount = async () => {
const storeAmount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount !== parseInt(storeAmount)) {
setAmount(parseInt(storeAmount));
}
}
useEffect(() => {
getAmount();
}, [props.item.id]);
otherwise, every time it renders you'll call setAmount which will trigger a rerender

React Native - Type Script: How to save dark mode toggle state even after turning off the application?

What is the correct way so that I can save the dark mode switch even after turning off the application?
I want to use the use-state-persist library to achieve the goal.
In my example I show app.tsx , Preferences.tsx, ViewField.tsx .
So that it will be possible to understand how the logic is built
DarkModeContext
import React from 'react';
interface DarkMode {
isDarkMode: boolean;
setDarkMode: () => void;
}
export const DarkModeContext = React.createContext<DarkMode>({} as DarkMode);
this is the app.tsx
import React, { useEffect, useState } from 'react';
import { syncStorage } from 'use-state-persist';
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const Drawer = createDrawerNavigator();
const initStorage = async () => await syncStorage.init();
const toggleDarkMode = () => {
setDarkMode(!isDarkMode);
};
return (
<DarkModeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
<NavigationContainer>
<Drawer.Navigator
drawerContent={SideMenu}
screenOptions={{
drawerPosition: 'right',
headerShown: false,
drawerType: 'front',
}}
>
<Drawer.Screen name='HomeScreen' component={StackNavigator} />
</Drawer.Navigator>
</NavigationContainer>
</DarkModeContext.Provider>
);
};
export default App;
this is the Preferences.tsx
import React, { useContext, useState } from 'react';
import ViewField from './ViewField';
import { DarkModeContext } from '~/context/DarkModeContext';
const Preferences = () => {
const { isDarkMode, toggleDarkMode } = useContext(DarkModeContext);
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggleDarkMode} />
</View>
);
};
export default Preferences;
this is the ViewField.tsx
import { View, Text, Switch } from 'react-native';
import React from 'react';
import styles from './ViewFieldStyles';
import { useContext } from 'react';
import { DarkModeContext } from '~/context/DarkModeContext';
type Props = {
title: string;
isEnabled: boolean;
setValue: () => void;
};
const ViewField = ({ title, isEnabled, setValue }: Props) => {
const { isDarkMode } = useContext(DarkModeContext);
return (
<View style={isDarkMode ? styles.optionViewDark : styles.optionView}>
<View style={styles.sameRowTextView}>
<Text style={isDarkMode ? styles.optionTextDark : styles.optionText}>{title}</Text>
<View style={styles.switchView}>
<Switch
trackColor={
isDarkMode
? { false: 'rgba(255, 255, 255, 0.38)', true: 'rgba(187, 134, 252, 0.38)' }
: { false: '#767577', true: 'rgba(4, 76, 163, 0.38)' }
}
onValueChange={setValue}
value={isEnabled}
/>
</View>
</View>
</View>
);
};
export default ViewField;
Keep in mind that there seems to be some problems in use-state-persist using Boolean values. Furthermore, the latest published version of this library is from 2020.
However, the use-state-persist library just seems to be a wrapper around AsyncStorage, which is very well maintained. I would encourage you to use this library instead.
In your case, this could be implemented as follows:
Store the actual setter of the state in the context,
Create an effect that accesses the async storage on mount of the application: if there exists a value for the corresponding key, set the state of the context, if not, then do nothing.
In the Preferences component, store a new state in the async storage as well.
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const contextValue = React.useMemo(() => ({
isDarkMode,
setDarkMode
}), [isDarkMode])
React.useEffect(() => {
const load = async () => {
const value = await AsyncStorage.getItem('isDarkMode');
if (value !== null) {
setDarkMode(JSON.parse(value));
}
}
load();
}, [])
return (
<DarkModeContext.Provider value={contextValue}>
...
};
In the Preferences component, set the state and save it to the local storage.
const Preferences = () => {
const { isDarkMode, setDarkMode } = useContext(DarkModeContext);
async function toggle() {
const newValue = JSON.stringify(!isDarkMode);
await AsyncStorage.setItem('isDarkMode', newValue);
setDarkMode(prev => !prev);
}
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggle} />
</View>
);
}

React Native: undefined is not an object (evaluating 'useContext.getItemsCount')

I'm a beginner on React Native and I am getting this error when getItemsCount is called.
*Please Click on the Links to see images
https://i.stack.imgur.com/wbwjZ.png
This is the code for CartIcon.js:
import React, {useContext} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {CartContext} from './CartContext';
export function CartIcon({navigation}){
const {getItemsCount} = useContext(CartContext);
return(
<View style = {styles.container}>
<Text style = {styles.text}
onPress = {() => {
navigation.navigate('Cart');
}}
>Cart ({getItemsCount()}) </Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 10,
backgroundColor: '#515b8c',
height: 40,
padding: 15,
borderRadius: 38/2,
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#ccc',
fontWeight: 'normal',
},
});
https://i.stack.imgur.com/ABYHm.png
This is the code for CartContext.js:
import React, {createContext, useState} from 'react';
import {getProduct} from './productService.js';
export const CartContext = createContext();
export function CartProvider(props){
const [items, setItems] = useState([]);
function addItemToCart(id){
const product = getProduct(id);
setItems((prevItems) => {
const item = prevItems.find((item) => (item.id == id));
if(!item){
return [...prevItems, {
id,
qty: 1,
product,
totalPrice: product.price
}];
}
else{
return prevItems.map((item) => {
if(item.id == id){
item.qty++;
item.totalPrice += product.price;
}
return item;
});
}
});
}
function getItemsCount(){
return items.reduce((sum,item) => (sum+item.qty),0);
}
function getTotalPrice(){
return items.reduce((sum,item) => (sum+item.totalPrice),0);
}
return(
<CartContext.Provider
value = {{items,setItems,getItemsCount,addItemToCart,getTotalPrice}}>
{props.children}
</CartContext.Provider>
);
}
https://i.stack.imgur.com/HsXoY.png
Taking a guess, but I would think that your component is outside of the provider, please check that your CartIcon is actually inside of the CartContext.Provider, otherwise it won't have access to it.
please add this to CartContext.js:
const useCartContext = () => {
const context = useContext(CartContext);
if (context === undefined) {
throw new Error('useCartContext must be used within a CartContextProvider');
}
return context;
};
and
export { CartProvider, useCartContext };
Go to App.jsx and wrap the whole app with
<CartProvider>
// your app
</CartProvider>
Then in CartIcon.js import useCartContext and replace
const {getItemsCount} = useContext(CartContext);
with
const { getItemsCount } = useCartContext();
Let me know what happens. The idea is to create a hook, which is nicer, but the issue here is that your component needs to be inside a provider for it to have access to the context.
The Reason for MySide getting Error is forget to use return statement while creating Global context .
Check that Side also .
** -> 1 more Side can be not Using ContextProvider or wrapping in the App.jsx File App.js File ( Mostly people Forget ).

Resolving promise issue with react native and Async Storage

I have spent days now and have read numerous articles and answers here and I can not wrap my head around this. Below is just my last attempt at this.
I just need to use data stored in Async Storage and use it inside App()
Can someone please take a look at this simple App() starting code and explain in the planest possible way how to resolve the promise here.
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import Icon from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '#react-native-async-storage/async-storage'
export default async function App () {
let [userData, setUserData] = useState({})
useEffect(() => {
storeData('test2')
getItem()
}, [])
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
const getItem = async () => {
const value = await AsyncStorage.getItem('#storage_Key')
return value
}
userData = getItem()
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
The <Text>Local storing: {userData}</Text> is allways a unresolved proise object.
I had some success with previous code where console.log(userData) did actually produce the wanted value but it is still not useable inside <Text>. I just dont get it.
Thank you in advance and please keep in mind I'm new to react native.
EDIT:
latest attemt:
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import Icon from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '#react-native-async-storage/async-storage'
export default async function App () {
let [userData, setUserData] = useState({})
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
storeData('test2')
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_Key')
if(value !== null) {
console.log(value)
return value
}
} catch(e) {
// error reading value
}
}
userData = await getData()
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
Now I get for some reason 4 test2 , correct values consoled logged but still get an error:
Error: Objects are not valid as a React child (found: object with keys {_U, _V, _W, _X}). If you meant to render a collection of children, use an array instead.
which is a promise object, and app fails to build.
Because you are not setting the state and getting the item on the renderer, not on any useEffect which is also a bad practice.
Try this
export default async function App () {
let [userData, setUserData] = useState('')
useEffect(() => {
storeData('test2')
}, [])
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
getItem()
} catch (e) {
// saving error
}
}
const getItem = async () => {
const value = await AsyncStorage.getItem('#storage_Key')
setUserData(JSON.stringify(value))
}
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
Fixed it:
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import Icon from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '#react-native-async-storage/async-storage'
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
storeData('test2')
export default function App () {
let [userData, setUserData] = useState('')
useEffect(() => {
getData()
}, [])
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_Key')
if (value !== null) {
console.log(value)
setUserData(value)
userData = value
}
} catch (e) {
// error reading value
}
}
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
there shouldn't have been async in export default function App () {
getData() get data should have been iniciated inside useEffect
useEffect(() => {
storeData('test2')
getData()
}, [])
I have also set the state inside getData()
userData = value

Orders are not displaying on first render but when I press Ctrl+S(Save) orders are displayed

import React, {useEffect} from 'react';
import {View, Text, StyleSheet, FlatList, TouchableOpacity} from 'react-native';
import Card from '../components/Card';
import {useState} from 'react';
import {useIsFocused} from '#react-navigation/native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const Orders = ({route, navigation}) => {
const [userID, setUserID] = useState('');
const [orders, setOrders] = useState([]);
const isFocused = useIsFocused();
async function getData() {
try {
const value = await AsyncStorage.getItem('UserID');
if (value !== null) {
console.log('USERID is ' + value);
setUserID(value);
}
} catch (e) {}
}
function fetchOrders() {
fetch(
'https://somewebsite/product/GetOrdersByUserID?userid=' +
//'1249b39a-ded0-4522-a263-f905ac30e5a3',
userID,
)
.then(response => response.json())
.then(responseJson => {
setOrders(responseJson);
})
.catch(error => {
console.error(error);
});
}
getData();
useEffect(() => {
//getData();
fetchOrders();
console.log('UserID inside useffect:: ' + userID);
console.log('inside useEffect');
}, [isFocused]);
return (
<View>
<View style={styles.container}>
<FlatList
scrollEnabled={true}
data={orders}
renderItem={({item}) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('OrderDetails', {
orderID: item.id,
});
}}>
<View style={styles.viewPP}>
<Card style={styles.cardPP}>
<Text style={styles.text}>Order ID:{item.id}</Text>
<Text style={styles.text}>Total: ₹{item.total}</Text>
<Text style={styles.text}>Placed: {item.placed}</Text>
<Text style={styles.text}>Status: Delivered</Text>
</Card>
</View>
</TouchableOpacity>
)}></FlatList>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 10,
},
text: {
fontWeight: 'bold',
alignContent: 'center',
},
cardPP: {
margin: 10,
},
});
export default Orders;
My issue is I am not getting the UserID on first render but when I press Ctrl+S(Save operation) I am able to get the UserID and hence the Orders are displayed.
My issue is I am not able to fetch the UserID on first render.
I have tried console.log(UserID) and it's blank the first time as above.
When I put the UserID directly as 1249b39a-ded0-4522-a263-f905ac30e5a3 in 'https://somewebsite/product/GetOrdersByUserID?userid=' inside fetchOrders() Orders are displayed without any issue on the First render.
Please help me out.
State updates are not synchronous. Like if you call setUserId, it doesn't synchronously set the state, for you to consume it instantly. It's managed asynchronously through React internally. Better if you follow your Promises and pass parameters, etc. Let me know if I missed anything
const Orders = ({route, navigation}) => {
const [userID, setUserID] = useState('');
const [orders, setOrders] = useState([]);
const isFocused = useIsFocused();
async function getData() {
try {
const value = await AsyncStorage.getItem('UserID');
if (value !== null) {
console.log('USERID is ' + value);
setUserID(value);
return value;
}
throw new ReferenceError("UserID is null");
} catch (e) {
return '';
}
}
function fetchOrders(userId) {
fetch(
'https://somewebsite/product/GetOrdersByUserID?userid=' +
//'1249b39a-ded0-4522-a263-f905ac30e5a3',
userID,
)
.then(response => response.json())
.then(responseJson => {
setOrders(responseJson);
})
.catch(error => {
console.error(error);
});
}
useEffect(() => {
getData()
.then((v) => fetchOrders(v));
}, [isFocused]);

Categories

Resources