How does AsyncStorage work with React Navigation? - react-native

I tried to get a value from AsyncStorage in my react native application, but it comes out as an empty string first.
Lets say I have 2 components, Home and About.
The About screen is where I'm getting my AsyncStorage values.
I was able to use createBottomTabNavigator to create navigation tabs at the bottom of my screen so I can toggle between Home and About.
When I press into my About screen, I tried to console log the value, but its only an empty array. If I use hooks (or useEffect) like so:
const [data, setData] = useState([])
const asyncFunctionData = async () => {
try {
const storageData = await AsyncStorage.getItem('key_data');
setData(JSON.parse(storageData));
console.log(data);
} catch (e) {}
}
useEffect(() => {
asyncFunctionData();
}, [data]);
The component will continuously execute for some reason, but after a couple of execution, I can finally get the value of AsyncStorage. But the issue is why is it executing multiple times and why do I get an empty array at first? I thought having [] will only execute the useEffect once or when there's an update.
Does AsyncStorage have some type of effect on the continuous execution? Also, does AsyncStorage not get any value on app load?

Your hook doesn't depend on data that you set after getting values from AsyncStorage, also you should define async function inside useEffect
const [data, setData] = useState([])
useEffect(() => {
const asyncFunctionData = async () => {
try {
const storageData = await AsyncStorage.getItem('key_data');
setData(JSON.parse(storageData));
} catch (e) {}
}
asyncFunctionData();
}, [setData]);`

Related

How to update the previous Screen's useState value and refresh the screen on click of goBack()

I want to update the previous screen's useState value and refresh the screen on click of goBack(), I know there is a useIsFocused() to do that but using this screen it is refreshing every time i goBack to the screen, suppose there is a ScreenA and ScreenB, So when user is performing any action in ScreeB then I am dispatching a value using redux and on goBack I am updating the useState value and refreshing the screenA, But it not working, I don't know what's the problem, Please help.
ScreenB
const leaveGroup = () => {
hideMenu();
callLikeMindsApi(`URL`, httpMethods.HTTP_PUT)
.then(response => {
if (response && response.data && response.data.success) {
hideMenu();
dispatch(updateHomeScreen(true));
props.navigation.goBack();
}
})
.catch(error => {
Alert.alert('FAILED', error.response.data.error_message);
});
};
And In ScreenA
const {updateValue} = useSelector(state => state.homeReducer);
useEffect(() => {
console.log('updateValue-1234', updateValue);
}); // it is printing true in console if (updateValue) { setOffset(1); setTotalPages(1); setMyChatRooms([]); getUserData(); } }, [isFocused]);
But
setOffset(1);
setTotalPages(1);
setMyChatRooms([]);
values are not updating, if I remove the if(updateValue){} and writing like this
useEffect(() => {
setOffset(1);
setTotalPages(1);
setMyChatRooms([]);
getUserData();
}, [isFocused]);
then code is working as expected, But it is refreshing every time I come back to the screenA and I want to refresh conditionally.
instead of props.navigation.goBack(); do
props.navigation.navigate('ScreenA',{details});
with the prop that you want pass
in screen A get that value in state from route params
ScreenA = (props) => {
const [navigateState] = useState(props.route.params.details|}{});
---while navigating to Screen B---
props.navigation.navigate('ScreenB',{details: navigateState});
...
I can't get full your code and understand the full logic but if you have already implemented redux you should use it like this:
const {updateValue} = useSelector(state => state.homeReducer);
useEffect(()=>{
if (updateValue) {
// update your screen juse by setting some states on the current component.
} else {
// don't update
}
}, [updateValue])
if you dispatch true value for updateValue whereever in your app then screen-A would be updated regardless it's getting focus or not. if you want to update screen-A only when it's on focus you should add focus condition in useEffect.

How to call API inside expo SplashScreen?

I'm new in react native so I can't figure out how to add an API call inside SplashScreen in react -native app. The context - I'm building a react-native app expo, which on app load should send API GET request to the backend to get order data, and based on that data I'm either displaying screen A(delivered) or B(order on it's way). I want to add this API call inside the SplashScreen when app still loads so when app is loaded there is no delay in getting API data and displaying screen A/B.
I have a simple useEffect function to call API like this:
const [data, setData] = useState{[]}
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(url);
if (response.status.code === 200 ) {
setData (response.data) // to save data in useState
}
} else if (response.status.code != 200) {
throw new Error();
}
} catch (error) {
console.log(error);
}
};
getData();
}, []);
and then in the return:
if (data.order.delivered) {
return <ScreenA />
}
else if (!data.order.delivered) {
return <ScreenB />
else {return <ScreenC />}
The issue is that sometimes if API is slow, then after splash screen app has a white screen, or ScreenC can be seen. How can I call API in the splashscreen while app is loading and have a nicer ux?
you can make a custom hook with simple UseState and put it after you've fetched your data
const [loading, setLoading] = useState(true)
...
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(url);
if (response.status.code === 200 ) {
setData (response.data)
// When data is ready you can trigger loading to false
setLoading(false)
}
...
and After that, you can use a Simple If statement on top of your app.js file
like this
if (!loaded) {
return <LoadingScreen/>; // whetever page you want to show here ;
}
you can use expo expo-splash-screen to achieve this goal:
call this hook on mount...
import * as SplashScreen from 'expo-splash-screen';
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// Keep the splash screen visible while we fetch resources
await SplashScreen.preventAutoHideAsync();
// Pre-load fonts, make any API calls you need to do here
await Font.loadAsync(Entypo.font);
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
you can also check expo doc

Getting updated redux state after dispatch with react hooks

I'm using the action expensesActions.getExpenseList to get a list from the database, which in turns updates the store in expense.expenseList
I'm calling the action inside useEffect hook and would like to get back the list from the store once it's retreived.
My code below is not working because the order is incorrect, if I refresh (with save) I do have the list. How can I change the code so my list is retreived once the actions is complete?
const fetchedList = useSelector(state => state.expense.expenseList);
// Get expense list
useEffect(() => {
const loadList = async () => {
setIsLoading(true)
await dispatch(expensesActions.getExpenseList())
calculateAverageExpense()
}
loadList()
}, [dispatch]);
You can have a second useEffect like this:
useEffect(() => {
if (fetchedList.length) calculateAverageExpense();
}, [fetchedList])
This will execute calculateAverageExpense every time your list change.

How to use useFocusEffect hook

As the docs https://reactnavigation.org/docs/en/next/use-focus-effect.html,
"Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc."
I'm trying to use useFocusEffect to fetch data everytime that the user go to that page.
on my component I have a function which dispatch an action with redux to fetch the data:
const fetchData = ()=>{
dispatch(companyJobsFetch(userDetails.companyId));
};
Actually I'm using useEffect hook to call fetchData(), but I'd like to fetch data everytime that the user go to that page and not only when rendered the first time.
It's not clear from the documentation how to use useFocusEffect and I'm not having success on how to do it.
Any help?
The docs show you how to do it. You need to replace API.subscribe with your own thing:
useFocusEffect(
React.useCallback(() => {
dispatch(companyJobsFetch(userDetails.companyId));
}, [dispatch, companyJobsFetch, userDetails.companyId])
);
For version react navigation 4.x, you can use addEvent listener
useEffect(() => {
if (navigation.isFocused()) {
resetReviews(); // replace with your function
}
}, [navigation.isFocused()]);
OR
useEffect(() => {
const focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action
_getBusiness({id: business?.id}); // replace with your function
});
return () => {
// clean up event listener
focusListener.remove();
};
}, []);
For later version 5.x, you can use hooks to achieve this
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}

React Native - AsyncStorage screen update content on load

Have looked at others solutions, but they don't seem to be good in my case.
I have a Utilities.js file:
const setItem = async (value) => {
if (!value) return;
AsyncStorage.setItem('#my_key', value);
};
const getItem = async () => {
var val = await AsyncStorage.getItem('#my_key');
return val;
};
All the users' input are being saved in the AsyncStorage via code on Screen1:
Utilities.setItem('value')
Once data is saved we can go to Screen2 to read up the AsyncStorage via the getItem() method put in ComponentDidMount method:
componentDidMount = async () => {
let asyncValue = await Utilities.getItem();
let objFromAsyncValue = JSON.parse(asyncValue);
this.setState({
storage: objFromAsyncValue
})
}
All works well if I open Screen2 for the 1st time - all saved data is being shown, but going back and adding additional values for AsyncStorage obj is not being updated on Screen2 - but asyncstorage has more items added.
So far have tried triggering method:
this.forceUpdate()
and checking if the event onDidFocus has been triggered on load:
<NavigationEvents onDidFocus={ alert('Scren refreshed')} />
I know component rendering is state based, but in my instance I have no states to be updated, only AsyncStorage stateless object.
How can I refresh the screen and/or just read the updated content of AsyncStorage object?
I think you're assuming that Screen2 mounts every time you focus it. This may not be necessarily true. What you should do is move your getItem call inside another method and call it onWillFocus.
Like this,
onFocus = async () => {
let asyncValue = await Utilities.getItem();
let objFromAsyncValue = JSON.parse(asyncValue);
this.setState({
storage: objFromAsyncValue
})
}
And then,
<NavigationEvents onDidFocus={ alert('Scren refreshed')} onWillFocus={this.onFocus}/>
In your case, I would use a context, where your provider is the the content the user type and gets saved to the asyncstorage, the consumer would be the screen 2. That way you only need to access the asyncstorage on screen 1 and screen 2 will always be up to date to whatever has been typed and saved on screen 1
See: https://reactjs.org/docs/context.html