How to write to Redux store outside of a React component? - react-native

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)

Related

Using useDispatch and useSelector inside a function

I am trying to call dispatch from inside a non-component function that handles api calls. The structure is as follows:
-MainApp.js
-scripts/
--auth.js
Inside scripts/auth.js it looks like this:
export async function signIn(nav) {
const dispatch = useDispatch();
try {
...
dispatch(setIsAuthed(true));
}
...
}
I call signIn() from and onPress. I am getting an invalid hook call. What am I doing wrong?
Hooks are functions that let you “hook into” React state and lifecycle
features from function components
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks
You should import the store and call store.dispatch to dispatch an action
The advice from Yilmaz is wrong. DO NOT DO THAT!
You should never import the store directly into the rest of the codebase. Instead, if you really must access the store elsewhere, inject it from the entry point.

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.

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.

Redux-Persist with React-Native-Background-Fetch

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.

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