React Native - AsyncStorage screen update content on load - react-native

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

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.

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 does AsyncStorage work with React Navigation?

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]);`

Data retrieve late from `asyncStorage` in react-native in drawer navigation

I have a user preferences screen in my app in which data is managed using asyncStorage. Whenever I change some value in preferences, I need to reflect that change in other screens of the app. But it does not show changes immediately but shows them when I reload the app. What should I do ..?
I am fetching data using: multiGet() in ComponentWillMount() and ComponentDidMount() and transitioning between screens with drawerNavigation.
I have even tried to use a global variable to reflect the changes but I does not help. Should I use redux? What should I do ? Thanks in advance.
use async-await to fetch data from asyncStorage
like this
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
// update your ui
console.log(value);
}
} catch (error) {
// Error retrieving data
}
};

Refresh Component on navigator.pop()

I'm using React Native's Navigator. Is there anyway to refresh the component so when I pop back to it, it'll make a new API call and grab the updated data to display in the component. I found a few similar questions, but no good answer...
Adding Api Call in callBack using a subscription. sovles the issue
componentDidMount() {
this.props.fetchData();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
}
You can send a callback function to nextscene from previous one as a prop.
this.props.navigator.push({
name: *nextscene*,
passProps: {
text: response,
callBack: this.callback
});
async callback(){
await ....//make new api request grab the udpated data
}
Then in your nextscene you call callback method and then pop. You can also send parameters
this.props.callBack()
this.props.navigator.pop()
When pop () to refresh before a page is not a good idea
You can try DeviceEventEmitter object
previous page DeviceEventEmitter.addListener('xxx', callback) in componentDidMount
current page DeviceEventEmitter.emit('xxx', anythingInCallback...) before pop()
ps:previous pageDeviceEventEmitter.removeAllListeners('xxx') in componentWillUnmount
I doubt you're still looking for an answer to this, but holy crap has this kept me up tonight. I'm very new to React Native, but I finally had some success.
The React Navigation API docs have a section for adding event listeners! Check it out! I shared some of my own code below too.
This is an example event handler in a Component that is the top screen of the StackNavigator stack. It grabs the current state and saves to the backend using an API call. After completion, StackNavigator's pop is called.
handleSubmit = () => {
const { value, otherValue } = this.state
addThingToDatabase({ value, otherValue })
.then(() => this.props.navigation.pop())
}
Now over to the other Component which is the screen "underneath" in the StackNavigator stack. This is screen being shown after the "pop". Here's what I used to have in ComponentDidMount.
componentDidMount() {
const { index } = this.props.navigation.state.params
getAllThingsFromDatabase({ index })
.then(({ arrayOfThings }) => this.setState({
index,
arrayOfThings
}))
}
But the Component wouldn't update with the new thing, until addListener! Now I have pretty much the same code except it's in the constructor. I figured I only need to run it one time, and I need to store it too.
constructor(props, context) {
super(props, context)
this.state = {
index: null,
arrayOfThings: []
}
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
(payload) => {
const { index } = payload.state.params
getAllThingsFromDatabase({ index })
.then(({ arrayOfThings }) => this.setState({
index,
arrayOfThings
}))
}
)
}
Note that the docs also mention unsubscribing the event listener using the .remove() function. I put that in ComponentWillUnmount().
componentWillUnmount() {
this.willFocusSubscription.remove()
}
There are four different events to subscribe to. I went with willFocus thinking it'll update before the screen is seen.
You should save the state of the page and emit an action in componentDidMount since it is invoked immediately after a component is mounted.
References:
https://facebook.github.io/react/docs/react-component.html
https://github.com/ReactTraining/react-router
ADDED
Since your component has been already mounted you should listen ComonentWillReceiveProps instead.
The simple way is to use react native navigation resetTo function. It will replace the top item and pop to it.
If we do like this componentWillReceiveProps will call. So we can provide the API calls in that function and make it simple.
for more details https://facebook.github.io/react-native/docs/navigatorios.html#resetto