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

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
}
};

Related

React native redux cause the UI to stop responding

I am using react native to make an app. I made an app that downloads manga books and views them when I start downloading it shows a progress bar which works fine but if I move to another page and come back to the downloading page I don't see the progress bar but the downloading is still going on in the background. I got a suggestion to use redux. I used redux and I modify the store whenever the downloading percentage changes but it causes my app to not respond. I am stuck here. what I do now.
Note: I am using the pure component.
here is the reducer code
const initailstate = {};
const reducerfunc = (state = initailstate, action) => {
switch (action.type) {
case "setpercentage":
state = { ...state };
state[action.manganame] == undefined
? (state[action.manganame] = {})
: null;
state[action.manganame][action.indexid] = {
chaptername: action.chaptername,
percentage: action.percentage,
manganame: action.manganame,
};
return state;
default:
console.log(state);
return state;
}
};
export default reducerfunc;
Here is the progress bar callback where I am dispatching it
callback = async (downloadProgress) => {
try {
if (Math.sign(downloadProgress.totalBytesExpectedToWrite) == 1) {
const progress = parseInt(
(downloadProgress.totalBytesWritten /
downloadProgress.totalBytesExpectedToWrite) *
100
);
this.props.dispatchsetpercentage({
type: "setpercentage",
percentage: progress,
indexid: this.indexofchapter,
chaptername: this.chaptername,
manganame: this.details.name,
});
} else {
}
} catch (e) {
console.log("error of callback " + e.message);
}
};
I think my UI stopped responding because it updates a lot of time and the app re-renders a lot of time
IS there any way to store the progress percentage globally and show it whenever the user comes back to the downloading page?
I want just like youtube or any other app which downloads things
they show the progress whenever we come back to the download page.
please help me :(

In which file do I set a key in react native async storage for the first time the app starts?

I am developing a react native app and I would like data persistence. Async storage is working fine as I expect it to, however the problem is that I initialize a key 'servers' in a component and and call the setItem function in componentDidMount function. Now because of this every time I terminate the app and run it again all the data from the previous session is removed as the 'servers' key is reset when the component is mounted. I understand why this is causing the problem so I want to know where should I set the key in my code that it will not reset on every time my component mounts.
This is the function that I call in componentDidMount. This is the only way to declare a key in Async storage correct? because without it I would not be able to call getItem.
const save = async () => {
try {
server_list = await AsyncStorage.setItem('server_list', JSON.stringify({servers: []}));
}
catch (e){
console.log("Failed to load")
}
}
You can achieve this by filling the AsyncStorage only as long as there is no data under the server_list key.
This would look like this:
setServerList = async (value) => {
try {
const serverList = await AsyncStorage.getItem('server_list');
if(serverList === null) {
await AsyncStorage.setItem('server_list', value)
}
} catch(e) {
// save error
}
console.log('Done.')
}
You can still call this in the componentDidMount and your server list will no longer be overwritten

refetch usequery when go back to previous screen not working in react native

I have 2 page, Page A (current page) and page B (next page). I am using react-native-router-flux as navigation. When go back to page A from page B (Actions.pop()) i want to refetch usequery so i put code like this in page A or component A
const { loading, data, refetch: refetchData } = useQuery(QUERY_GET_STATUS, {
fetchPolicy: 'network-only',
});
useEffect(() => {
if(refresh){
refetchData();
}
}, [refresh])
variable refresh is redux state has value true and false. Before go back to page A refresh state will be update first into true. but i found the issue that refetch query not working. Do you have any solution to resolve it ?
If you wanna call function every time when screen on front then you this hook
import { useFocusEffect } from '#react-navigation/native';
import React{useCallback} from 'react'
useFocusEffect(
useCallback(() => {
//function
}, [])
);
I had a similar problem with a different package. I'm not totally sure if this might work for you but I think with react-native-router-flux, you have access to currentScene. So you could add an effect that is called whenever the route changes
const currentScene = Actions.currentScene;
useEffect(() => {
if(refresh && currentScene === "whatever-scene-you-are-on"){
refetchData();
}
}, [refresh, currentScene])

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

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