Redux-Persist with React-Native-Background-Fetch - react-native

I am creating a React-Native app that fetches data from an API as a background service.
I have looked around the web if I can manually rehydrate the store with the data fetched during the background task, but I could not find anything.
Is it possible to rehydrate the redux-persist store manually while the app is killed, from a background 'service' task?

For the people still wondering, if it is possible to use react-native-background-fetch for scheduling ANY task, it is completely fine as long as it does not touch the UI eg. (AsyncStorage, Redux-Persist, Realm, DB...) is not directly related to invoking change in the UI, so it is completely fine to use.
In my particular case, I am using the slowest option - AsyncStorage - to persist a props sort of object which I use on global App level and pass derived data onto my components:
// Example of HeadlessTask implementation
import BackgroundFetch from 'react-native-background-fetch'
import AsyncStorage from '#react-native-community/async-storage';
const HeadlessTask = async () => {
// Prepare data - fetching from API, other transformations...
let propsObject = {};
AsyncStorage.setItem(ITEM_KEY, JSON.strigify(propsObject))
.then(() => {
console.log('[AsyncStorage] Object Saved!');
// For iOS specifically we need to tell when the service job
// is done.
BackgroundFetch.finish();
})
.catch((e) => {
console.log('[AsyncStorage] Error saving object: ', e);
BackgroundFetch.finish();
});
}
P.S. See https://github.com/transistorsoft/react-native-background-fetch to see how to install and implement Background Fetch.

Related

How to write to Redux store outside of a React component?

I have a React Native app where I am using HeadlessJS to call a handler on receipt of a Firebase Cloud Messaging notification.
Inside my handler which is a function, not a React component, I am accessing the Redux store using the following method:
import store from '../redux/store';
const backgroundNotificationHandler = async message => {
const state = store.getState();
...
My question is, how can I update the store in a a way that isn't a 'hack'?
Currently I have the following line in the function:
state.user.wokenFromBackgroundListener = true;
Surprisingly is works, but this is without dispatching an action or using a reducer.
Is there a better way to do it?
I can't dispatch an action because the function is not a component and it is not a component because it can't be - it requires a function as in the docs.
My code in index.js is:
AppRegistry.registerComponent(appName, () => App);
firebase.messaging().setBackgroundMessageHandler(backgroundNotificationHandler);
Dispatching from component props is just an added functionality provided by react-redux. You can still use plain redux api, in this case you can dispatch to store using store.dispatch
store.dispatch({ type: 'SOME_ACTION', payload: [1,2,3] })
However I'm not sure if you should be doing that, I haven't used HeadlessJS myself but I would first check and make sure that these task handlers are actually being run in the same context your app is running (e.g. confirm that they share store instance with your app, and NOT create a separate store just because you import store in file with the handler)

How do MongoDB Stitch SDK's work in regards to client instantiation or how does Stitch.defaultAppClient.getServiceClient work?

I'm using expo to build out a React Native application and I'm running into issues when attempting to write code that accesses remote MongoDB servers. I'm attempting to use MongoDB's provided Stitch SDK's for React Native.
When running
const mongoClient = Stitch.defaultAppClient.getServiceClient(RemoteMongoClient.factory, "mongodb-atlas");
I'm running into the following error:StitchServiceError: service not found: 'mongodb-atlas'
When my app initializes in my main App component, I'm initializing the default client using Stitch.initializeDefaultAppClient per the recommended documentation. Based on my debugging logs, this part is working correctly and I'm able to authenticate with the service correctly and I am storing the client in the App component's state. I'm running the loadClient method in the constructor of my main App component.
_loadClient() {
console.log("Loading Stitch client");
Stitch.initializeDefaultAppClient("xxxxxxxxxxxxxxx").then(client => {
this.setState({ client });
this.state.client.auth
.loginWithCredential(new AnonymousCredential())
.then(user => {
console.log(`Successfully logged in as user ${user.id}`);
this.setState({ currentUserId: user.id });
this.setState({ currentUserId: client.auth.user.id });
})
.catch(err => {
console.log(`Failed to log in anonymously: ${err}`);
this.setState({ currentUserId: undefined });
});
});
}
For more context: I'm executing the getServiceClient function in a separate react saga so that I can fetch data behind the scenes based on actions that are dispatched within the application. I'm calling getServiceClient inside a function that gets called upon every dispatch of a specific action. All of this is exported to a single async function which is then applied as saga middleware enhancer to a store.
I think I'm not able to retrieve the service client because the defaultappclient isn't initialized within the context of the saga because of the way sagas work (from my understanding) but I need more insight into how getServiceClient() works.
I ended up storing the client in a local instance in the saga js file so that the instance is available for all sagas and I plan on keeping all sagas within this file. I am using asynchronous functions to ensure that the app client is initialized prior to binding any client requests to redux actions.
Example:
let appClient;
function* initAppClient() {
console.log("Initializing Stitch Client");
yield Stitch.initializeDefaultAppClient("client-identification-here ").then(client => appClient=client);
}
export default function* rootSaga() {
yield initAppClient();
yield takeEvery('ACTION HERE', uploadState);
}
The downside to this approach is that this instance won't be available to the rest of my react application and I won't be able to use Stitch functionality to update anything through the actual react application. This works for me as I only plan on using Stitch when state changes within my application and this decouples any server/remote data operations from react application functionality which focuses on presentation, routing, etc. If I want to use Stitch within my react application, I would have to initialize another client within react's context.

Redux: Is there any smart way to avoid the antipattern of importing store for helper files?

I'm currently build a React Native application using Redux the state management and Firebase Cloud Messaging for my real time communication.
To use FCM in the background on Android you are required to create file called bgMessaging.js.
// #flow
import firebase from 'react-native-firebase';
// Optional flow type
import type { RemoteMessage } from 'react-native-firebase';
export default async (message: RemoteMessage) => {
// handle your message
return Promise.resolve();
}
My problem is that I need to dispatch an action here. The only solution I found for this was to import my store and call store.dispatch(). I've been told this is an anti-pattern and considered bad practice. What else could I do that is not an anti-pattern?
Edit:
Mark Erikson himself was so kind and gave his opinion on this topic. Thanks Mark!
I've also come into the same scenario when writing my application. My approach to my React Native App was to create React Components, but deal with a lot of my data fetching/handling outside of React Components - because I didn't know whether I'd be using React all of the time, but wanted to create re-usable modules for my other Type/JavaScript projects. For example I'd created a few helper files which dealt with various APIs, but when I integrated Redux into my project - I had the same issue. How do I dispatch without re-adding in your store (as I can see this can be considered Anti-Pattern).
Reading into a few articles, there's no real place to suggest that this approach is 'Anti Pattern'. A lot of the time, stores are imported within the React Context (which is doesn't need to be) - this is Anti Pattern. In your use case, I don't really see how that can be Anti Pattern, I certainly came to this conclusion when I was doing the same thing. In my view 'Common' parts of the app should be used by many other parts of the application.
What I'm seeing is that you need to provide a function which has a single arg, typed as RemoteMessage which returns a promise, and you need to provide that function to registerHeadlessTask (wrapped in another function for some reason..)
So what if your bgMessaging file looked like this..
// #flow
import firebase from 'react-native-firebase';
// Optional flow type
import type { RemoteMessage } from 'react-native-firebase';
export default store => {
return async (message: RemoteMessage) => {
// handle your message
store.dispatch();
return Promise.resolve();
}
}
and in your index you did..
import bgMessaging from './src/bgMessaging';
const store = redux.createStore();
const bgMessagingFn = bgMessaging(store);
// Current main application
AppRegistry.registerComponent('ReactNativeFirebaseDemo', () => bootstrap);
// New task registration
AppRegistry.registerHeadlessTask('RNFirebaseBackgroundMessage', () => bgMessagingFn);

Correct way to use initialize firestore in react-native expo app

Have seen two different ways to initialize firestore in a react-native app and would like to know what the differences between the two are. The method shown in the firestore docs (https://firebase.google.com/docs/firestore/quickstart#initialize) looks like
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
export fs = admin.firestore();
while the "firebase" way (as seen in this expo post: https://forums.expo.io/t/open-when-an-expo-firebase-firestore-platform/4126/29), which is the way I currently use and appears to work, looks like
import * as firebase from 'firebase';
import 'firebase/firestore';//for using firestore functions, see https://stackoverflow.com/a/50684682/8236733
import { firebaseConfig } from './firebase-credentials';//WARN: gitignored, exports object containing firebase (web)app credentials
// Initialize Firebase
// why in separate file? see https://github.com/zeit/next.js/issues/1999 and https://ilikekillnerds.com/2018/02/solving-issue-firebase-app-named-default-already-exists/
// firebase.initializeApp(firebaseConfig);
try {
firebase.initializeApp(firebaseConfig)
/*WARN:
#firebase/firestore:, Firestore (5.0.4):
The behavior for Date objects stored in Firestore is going to change
AND YOUR APP MAY BREAK.
To hide this warning and ensure your app does not break, you need to add the
following code to your app before calling any other Cloud Firestore methods:
const firestore = firebase.firestore();
const settings = {timestampsInSnapshots: true};
firestore.settings(settings);
With this change, timestamps stored in Cloud Firestore will be read back as
Firebase Timestamp objects instead of as system Date objects. So you will also
need to update code expecting a Date to instead expect a Timestamp. For example:
// Old:
const date = snapshot.get('created_at');
// New:
const timestamp = snapshot.get('created_at');
const date = timestamp.toDate();
Please audit all existing usages of Date when you enable the new behavior. In a
future release, the behavior will change to the new behavior, so if you do not
follow these steps, YOUR APP MAY BREAK.
*/
const fsSettings = {/* your settings... */ timestampsInSnapshots: true};
firebase.firestore().settings(fsSettings)
} catch (err) {
// we skip the "already exists" message which is
// not an actual error when we're hot-reloading
if (!/already exists/.test(err.message)) {
console.error('Firebase initialization error', err.stack)
}
}
export const fs = firebase.firestore()
The post linked to is the only instance where I could find someone else doing this, but again it does work for me (can read and write to firestore).
Very new to using firebase/firestore and would like to use the more 'correct' method. Is there any difference between initializing firestore in the app in these separate ways?
Import:
import * as firebase from 'firebase';
import 'firebase/firestore';
Then
const db = firebase.firestore();
https://github.com/invertase/react-native-firebase
This is a JavaScript bridge to the native Firebase SDKs for both iOS and Android therefore Firebase will run on the native thread.
It has a step-by-step instructions for react-native app integration with firebase.
One important thing is that you have to consider about your react-native version and firebase sdk version.
They do the same things though? The first one simply does it by declaring and expo does it by declaring it inline. You can do it however you like, but both of them do the same things

Async store enhancer?

I'm trying to build a version of https://github.com/elgerlambert/redux-localstorage for React Native. The catch is that the React Native version of localStorage — AsyncStorage — has an asynchronous API. Unless I'm missing something, this effectively means that I cannot return the enhanced store with the existing store enhancer API.
Is there a workaround for this, or is it a fundamental problem in the store enhancer API?
Why is it important for the persistence operation to complete synchronously? I would argue that for performance reasons, you explicitly do not want the IO operation to delay the processing of the action within the Redux cycle.
Depending on your application, it might make sense to not only persist your store state asynchronously, but even debounce to persistence further to allow the user interaction and the following rerender to complete before persisting the new state.
Here is a simplified version of how I have implemented lazy persistence in my apps:
import {debounce} from 'lodash';
/**
* Redux middleware for persisting store state on change
*/
export default function takeStateSnapshot(store) {
// wait for input to stop for a while to avoid unnecessary
// serialization and IO overhead during busy dispatches
const takeLazySnapshot = debounce(saveSnapshot, 250);
return next => action => {
const result = next(action);
takeLazySnapshot(store.getState());
return result;
};
}
async function saveSnapshot(state) {
try {
await AsyncStorage.setItem('my-app-state', JSON.stringify(state));
} catch (e) {
console.error('Error persisting application state', e);
}
}
This approach does not give you guarantees that the latest state will be persisted in case of abrupt app shutdowns, but in our case it does not matter, as the store app state is used as a cache and never trusted as a source of truth.