Dispatch an action in background app refresh with react native - react-native

I'm using react-native-background-fetch to receive app refresh events and have been struggling to dispatch an action (that fetches data) when it's triggered. I'm able to do this outside of redux but not when I dispatch the action.
BackgroundFetch.configure({
stopOnTerminate: false
}, async () => {
await store.dispatch(getItemsAction);
BackgroundFetch.finish();
});
Action:
export function getItemsAction() {
// <-- Reaches here
return async (dispatch, getState) => {
// <-- But not here
const items = await findAll();
dispatch(itemsRetrieved(items));
}
}
If not a solution, I'd like to get some insight into what's happening here.

First of all you need to call action creator
await store.dispatch(getItemsAction());
Then you'll need a middleware to handle functions as actions. I assume you are aware of redux-thunk.

If it's a headless task running in the background, It does not have access to the redux store from what I experienced.
You will want to use something like AsyncStorage (https://github.com/react-native-community/async-storage) when running the task as headless js, which is what happens when the app is running background events.

Related

Firebase authentication and redux-saga synchronizing state

I have a design question on how to manage firebase auth & redux saga states with react-native-firebase.
Example use-case
Let's start from the scenario that I have an app that uses the idToken for a variety of use cases, some in the views using information from the claims, and some in redux actions to make api calls.
Using redux-saga, I would expect to implement these two cases like so:
// in selectors.js
const getIdToken = (state) => state.idTokenResult?.token
const getUserRole = (state) => state.idTokenResult?.claims.role
// in view.js
const role = useSelector(Selectors.getUserRole)
// in actions.js
const idToken = yield select(Selectors.getIdToken)
With this in mind I want to make sure the idTokenResult is available & up to date in my state. I can do this we a few actions and reducers, by calling a login method & then relying on the dispatched event onIdTokenChanged to update my state on login & tokenRefreshes. Something like the following:
// in actions.js
function* onLogin(email, password){
yield call([auth(), 'signInWithEmailAndPassword'], email, password)
}
// This action would be called by an eventChannel which emits on each onIdTokenChanged
function* onIdTokenChanged(user){
yield put({ type: "UPDATE_USER", user: user, })
if (user){
const idTokenResut = yield call([auth().currentUser, 'getIdTokenResult'])
yield put({ type: "UPDATE_ID_TOKEN_RESULT", idTokenResult: idTokenResult, })
}
}
// in reducers.js
const reducer = (state = {}, action) => {
switch (action.type) {
case 'UPDATE_USER':
return { ...state, user: action.user };
case 'UPDATE_ID_TOKEN_RESULT':
return { ...state, idTokenResult: action.idTokenResult }
}
}
Problem
Here is when we run into a problem. I recently learned that the onIdTokenChanged is dispatched lazily, only when the getIdTokenResult() method is invoked link. This means that with the code above we cannot expect our state to be accurate, because when we call yield select(Selectors.getIdToken) it doesn't check getIdTokenResult() and therefore the onIdTokenChanged event is never dispatched.
Potential solutions
How do we overcome this problem?
Set up a timer which periodically calls getIdTokenResult() before the token expires, to trigger the event.
Should work, but defeats the purpose of having an onIdTokenChanged event. Also this means it will refresh the token hourly, even if it isn't needed or being accessed
Somehow call getIdTokenResult() in the selector?
It's an async method so it seems like an anti-pattern here and I'm not even sure it's possible
Use the library directly to fetch user states with auth().currentUser, and forget redux-saga
We lose the nice rerender functionalities that redux's useSelector provides. By accessing the state directly we'll need to figure out another way to trigger rerenders on auth changes, which defeats the purpose of using redux-saga
Something I didn't consider/implemented incorrectly?
Your suggestions are welcome and thanks in advance for you help! :)

AsyncStorage.removeItem(key) not removing items unless App restarted or after several minutes

I have a logout Button that binds to the following method.
async onLogoutButtonPress() {
await AsyncStorage.removeItem('accessToken');
await AsyncStorage.removeItem('mobileNumber');
await AsyncStorage.removeItem("businessId");
await AsyncStorage.clear();
this.props.navigation.navigate('Login');
}
I understand that AsyncStorage.removeItem is an asynchronous operation and it returns a Promise. However notice that I am using await that waits for Promise to resolve before navigating to Login.
I also tried the alternate then syntax
AsyncStorage.removeItem(('accessToken')).then(() => {console.log('resolved') },
() => { console.log('rejected') })
And all of them show as resolved in the console, but some how the items persist unless I close and restart the app. I am seeing this behavior on Android.
Why aren't the items getting removed until I restart the app ?
I am using expo for the build, if that matters.
I am using
import { AsyncStorage} from 'react-native';
Here's the react-native version.
"react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",

API response is not accessible in ComponentDidMount but in render I can use

Hi I am working on React Native app. I am using Redux and Saga. I call the API in componentDidMount.
async componentDidMount() {
let data = this.props.navigation.getParam("returnProductData");
if (data) {
console.log("Return Here");
this.props.getProductReturnAction(data)
this.setState({
returnQty:parseInt(this.props.product.item_ordered)-parseInt(this.props.product.already_return_qty)
});
console.log(this.state.returnQty,"Return quty"); //coming undefined
console.log(this.props.product, "product"); // undefined
console.log(this.props.product.item_ordered); //undefined
}
}
I have to set the state in componentDidMount for returnQty. But, state is not accessible here. It's working fine in render method. I can use all the product object. But, it is coming empty in componentDidMount. I tried using async and await but it's not working.
// Dispatch Methods
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{ getProductReturnAction, submitProductReturnAction },
dispatch
);
};
// Props
const mapStateToProps = state => {
return {
product: state.myOrdersReducer.returnProduct
};
};
I can't be able to find out the bug please help to find out the best solution.
When you are making API calls through redux/saga, you can not use async await, as the frameworks will just dispatch an action and return back, the listeners which are registered for the action will be triggered and then after they complete their work they will dispatch a new action and respect reducer will handle the response.
Explained above is general scenario.
In your scenario,
You are dispatching the action returned by getProductReturnAction which will give say GET_PRODUCTS action.
A saga would be registered for GET_PRODUCTS, say getProducts, this get invoked.
This will perform the API call once the response is received it will dispatch GET_PRODUCTS_SUCCESS along with the products data.
Corresponding reducer which handles GET_PRODUCTS_SUCCESS will get called and that updates returnProduct and as you are registered for that in your component the render method gets called (as the props are changed) and hence product data is available in your render method.
This is working perfectly correct. I don't see anything wrong here.
As the data is available in props use the same u do not need to do a setState again on that.

In vue.js, where can I place "app initialization" code?

Specifically, code that runs before the app actually loads. I'm using vuex and the first thing I want to do (regardless of what route the user is on) is to dispatch a getUser action to get currently user details from the API (or alternatively, redirect if not authenticated).
If I place it in my App.vue mounted component, I believe it might be too late? Don't children components load before parents?
If I get it right you want to do something before the application initialize. For that you can just perform async method in app initialization. Something like that as an example:
function initializeApp (vueCreated) {
return new Promise((resolve, reject) => {
switch (vueCreated) {
case false: // "prevue" initialization steps
console.log('vue not yet created, prevue steps happens')
// ...
setTimeout(_ => resolve(), 3500) // async call
break;
case true: // we can continue/prepare data for Vue
console.log('vue created, but waiting for next initialization steps and data')
// ...
setTimeout(_ => resolve('Mounted / shown when app ready'), 3500) // async call
}
})
}
initializeApp(false).then(_ => {
new Vue({
template: '#app',
data: {
content: null
},
async created () {
this.content = await initializeApp(true)
this.$mount('#app')
console.log('all inicialization steps done, data arrived, vue mounted')
}
})
})
I have found some article related to your question may be this help you out. Link
If you are using vue-router you can use beforeEach to prevent some routes of unauthenticated users.
You can read more here.
If you get stuck here provide code what you tried with router.
Also good example of using navigation guards.
Good luck!

Waiting for async before register component in react-native

I need to wait for async storage and then init app, because I store auth token here and want to show correct scene for user if he was authorised:
(async () => {
const viewer = JSON.parse(await AsyncStorage.getItem('viewer'));
// ...
const RootContainer = () => (
// ...
);
AppRegistry.registerComponent('yawaloo', () => RootContainer);
})();
I have moved to react-native 0.40.0 from 0.34.1 and now have an error "Module AppRegistry is not a registered callable".
In previous version everything were ok. How can I wait for some actions and then start render RootContainer?
One idea is to use splash screen. More specifically use a state in your RootContainer to determine whether to show a splash screen or your main UI. Set the state to false (show splash) initially then after you read the token from async storage, then set the state to true.
Part of why apps have splash screens is to deal with situation like this. HTH