React Native - call a parent function from a utility class - react-native

I've setup react native push notifications with Firebase and local push notifications - both work fine.
On receiving a notification when the app is in the foreground (opened and focused) I am calling a local push notification which works fine as well.
Then, when the user clicks on the local notification, in the utility class there is an OnNotification method that is called and I need to set some state there which of course doesn't work.
So instead, I am trying to callback a function on the parent file (App.js) which does has access to state management. problem is the function in App.js is not accessible in the utility class (it's not a class component).
How can this be achieved?
Thanks in advance :)
Below is an simplified example of both files.
// App.js
import React, {useState, useEffect} from 'react';
import Notifications from './src/utils/Notifications/Notifications'; // import the utility class.
const App = () => {
// handles remote notification when app is in the FOREGROUND
useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage = {}) => {
// CALL FUNCTION ON UTILITY CLASS
Notifications.localNotification(remoteMessage.data);
});
return unsubscribe;
}, []);
// THE CALLBACK FUNCTION PART
const [someState, setSomeState] = useState('');
const [someMoreState, setSomeMoreState] = useState('');
const myFunc = (returnedData) => {
setSomeState(returnedData.value_1)
setSomeMoreState(returnedData.value_2)
}
return (
<></>
);
};
export default App;
// Notifications.js - utityly class.
import PushNotification from 'react-native-push-notification';
class Notifications {
constructor() {
PushNotification.configure({
onNotification: function (notification) {
if (notification.userInteraction) {
// THIS IS TRIGGERED AFTER THE USER CLICKS THE NOTIFICATION - WORKS FINE.
// HOW TO CALL FUNCTION HERE THAT NEEDS TO HANDLE STATE (function is in app.js)
// example:
const returnedData = {
value_1: 'A',
value_2: 'B',
}
myFunc(returnedData); // this function is in app.js
}
},
});
}
localNotification(data) {
}
}
export default new Notifications();

Accessing a function in App.js from components is not a good approach. As I understand it, you only need to access this function to set some state variables. A much better solution to your problem would be to use a shared state like Redux or Context API.
You can then access this state from anywhere in your project and change it accordingly

Answering my own question in case it might help somebody.
The only thing that worked for me is to refactor the Notifications class into App.js inside a useEffect()

Related

React-Native retrieve API_URL from AsyncStorage and make in accessible in all app (for test purpose)

I am trying to retrieve the API_URL from AsyncStorage and make it accessible in all app, the storing and retrieving (in settings screen) is working fine but when I try to load the data in the App.js using useEffect hook, it returns null. Reloading the app is not working but as soon as I save the App.js (using CTRL-S) it works fine.
Please let me know the correct way to do this.
import React, { useEffect, useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
export default function App() {
const [hostState, setHostState] = useState(null);
const getAHostInfoAsync = async () => {
const hostInfo = AsyncStorage.getItem('host').then(
setHostState(hostInfo)
).then(
console.log(hostState)
);
};
useEffect(() => {
getAHostInfoAsync();
}, []);
module.exports = {
host: hostState
};
}
and using in another file:
import App from "../../../App";
const API_URL = App.host;
I think your issue is in the way you use async/then. instead of async await.
I am not 100% sure that this is your issue. But if I change my async/await function to use async/then the way you are having it, my IDE says that the variable (hostInfo) might not have been initialised. In any case, I think this is a better syntax than with then.
const getAHostInfoAsync = async () => {
const hostInfo = await AsyncStorage.getItem('host')
setHostState(hostInfo)
console.log(hostState)
};

why chatMsgStore.addChatMsg(bdmsg) does not effect the store?

store.js
import {useLocalObservable} from "mobx-react-lite";
function chatStore() {
return {
chatmsg: [],
setChatMsg(arr) {
this.chatmsg = arr
},
addChatMsg(msg) {
this.chatmsg.push(msg)
}
}
}
export const useChatStore = () => useLocalObservable(chatStore)
app.js
const App = () => {
const chatMsgStore = useChatStore()
const AppFunctions = {chatMsgStore}
useEffect(() => {
socket.on(activechat.chatid, (bdmsg) => {
chatMsgStore.addChatMsg(bdmsg)
})
return () => {
socket.off(activechat.chatid)
}
}, [activechat, chatMsgStore.chatmsg])
return (
<>
<AppContext.Provider value={AppFunctions}>
.....................
</AppContext.Provider>
</>
)
}
export default App;
fetch.js
async function getChatMessages(url, body, userStore, chatMsgStore) {
........
chatMsgStore.setChatMsg(firstResData)
........
on app load i add a socket listener which deps are activechat and chatMsgStore.
this listener is dynamic and must be changed when deps change.
the only purpose of this listener is to add a msg to the store and re-render the observer component
deps :
activechat - non store state
chatMsgStore.chatmsg - store state
why chatMsgStore.addChatMsg(bdmsg) does not effect the store? so deeply nested components inside App.js is not re-rendering.
otherwise i have a function getChatMessages which i import from custom hook deep inside App.js which sets the messages. this func is not a child of App.js and it is not wrapped with observer chatMsgStore.setChatMsg(firstResData) works! i can set the message so the observer component will re-render
how to make this code in useeffect above work?
Your App component is not wrapped with observer HOC so it won't react to observable values changes.
Wrap it like that:
const App = observer(() => {
// ...
})
or when exporting:
export default observer(App)
More info in the docs
you should use autorun from mobx in order to set correctly the reactivity in useEffect, here is a link to the doc that explains why and how use it.
But I think that you should not put chatMsgStore.chatmsg inside the deps array because you're not using it inside the useEffect.
If you can provide a working example maybe we can help you further.

React Native Navigation and Redux Persist

I am trying to integrate redux-persist with wix react-native-navigation. However, I am unable to find any examples or documentation stating the boilerplate code needed to integrate the both libraries.
I was wondering if anyone would like to share their solution if they have solved this issue ?
First of all, the basic setup should be the similar with or without react-native-navigation as described in the documentation in store.js:
import { persistStore, persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/es/storage' // default:
localStorage if web, AsyncStorage if react-native
import reducers from './reducers' // where reducers is an object of
reducers
const config = {
key: 'root',
storage,
}
const reducer = persistCombineReducers(config, reducers)
function configureStore () {
// ...
let store = createStore(reducer)
return store
// We'll skip persistStore for now
// let persistor = persistStore(store)
//return { persistor, store }
}
The persistStore call is commented out as we'll do it below. The persistStore method takes a callback in its third argument. The callback is executed after the state is restored/rehydrated. This is nice because this means we can delay starting the screen(s) until the state is rehydrated.
Let's assume you have the following bootstrap code in App.js:
store = configureStore()
registerScreens(store, Provider)
Navigation.startTabBasedApp({
tabs: [{...},]
})
Now we can add persistStore and wrap your bootstrap code in it like this:
store = configureStore()
persistStore(store, null, () => {
registerScreens(store, Provider)
Navigation.startTabBasedApp({
tabs: [{...},]
})
})
Note:
In v4, you pass config instead of null: persistStore(store, config, callback)
In case you're looking to integrate it with react-native-navigation v2, in App.js, make sure you call persistStore() inside the registerAppLaunchedListener() :
import { persistStore } from 'redux-persist';
...
Navigation.events().registerAppLaunchedListener(() => {
persistStore(store, null, () => {
Navigation.registerComponentWithRedux(...);
...
Navigation.setRoot({...})
...
})
})
Adding to his solution you can also use subscribe() to check if your user is still logged in. That way they don't need to sign in again if they completely close the app (for those users with a login system) and since it is only called once the store is persisted, you can start your app after this is checked.
import {Platform, AsyncStorage, AppState} from "react-native"
import {Navigation} from "react-native-navigation"
import {registerScreens} from "./routes"
import {Provider} from "react-redux"
import configureStore from "./stores/reduxStore"
import {Component} from "react"
const storage = configureStore()
registerScreens(Provider, storage.store)
let startapp = screen => {
Navigation.startSingleScreenApp({
screen: {
screen, // unique ID registered with Navigation.registerScreen
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarColor: "white",
statusBarTextColorScheme: "dark"
}, // override the navigator style for the screen, see "Styling the navigator" below (optional)
navigatorButtons: {} // override the nav buttons for the screen, see "Adding buttons to the navigator" below (optional)
},
drawer: {
left: {
screen: "Drawer", // unique ID registered with Navigation.registerScreen
passProps: {} // simple serializable object that will pass as props to all top screens (optional)
}
},
tabsStyle: {
// optional, add this if you want to style the tab bar beyond the defaults
tabBarButtonColor: "#ffff00", // optional, change the color of the tab icons and text (also unselected). On Android, add this to appStyle
tabBarSelectedButtonColor: "#ff9900", // optional, change the color of the selected tab icon and text (only selected). On Android, add this to appStyle
tabBarBackgroundColor: "#551A8B", // optional, change the background color of the tab bar
initialTabIndex: 1 // optional, the default selected bottom tab. Default: 0. On Android, add this to appStyle
},
appStyle: {
orientation: "portrait"
}
})
}
storage.persistor.subscribe(() => {
storage.store.getState().user.logged
? startapp("mainscreen")
: startapp("loginscreen")
})
We actually dont need redux-persist. We can make our own redux-persist with:
redux + store.subscribe(handlechange)
handleChange function will run when ever something changes in our store.
Also Using aync-await(promise) we are not blocking the main execution thread.
So Inside create store add something like:
store.subscribe(async ()=>{
try {
await AsyncStorage.setItem("store", JSON.stringify(store.getState()));
} catch (error) {
// Error
}
})
Then inside App.js(first component to load). use AsyncStorage.getItem('store'). Then update the store before app starts.
localstorage on the web is a synchronous function which blocks the main thread.
AsynsStorage in react-native doesn't blocks the main thread.

REDUX: understanding a bit the concept + react native

So, I am working on a pretty straight forward mobile app that has these scenes:
a list of people
person profile
add form
now, what I do, when I first load the LIST scene, I make an API call (I have a list component that I populate once I get results from the API... state.people).
All good here... when I tap on a person he's profile opens, no extra API calls, just passing the person object from state.people array.
All good here as well.
When I open ADD NEW person and send the form I make another API call (I post the information and get the new Object back)...
now the bit that is confusing to me.
What I would like is to update the LIST scene state.people by making another API call (get all again) after I get the OK confirmation from the POST.
and then navigate to Person's profile.
but, I am outside the scope of the LIST scene (I am in ADD NEW form). So, what would be the correct redux logic for this one?
The LIST component is already mounted... how do I communicate to LIST if I am on different scene
all these binding actions to components properties is confusing too... why can't redux act like a global hub that would always be accessible and would always retain it's state (at least on mobile app)
There is really a lack of real app examples... so far I see only very simplified examples that are not very useful on the grand scale to understand the whole flow
the store I have
/**
* STORE
*/
'use strict';
import { createStore, applyMiddleware } from 'redux';
import reducer from './_reducer';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware()
));
export default store;
and the actions I have:
import * as constants from '../../constants/constants';
import request from '../../utils/request';
export const getAll = () => ({
type: constants.PEOPLE_FETCH,
payload: request(constants.API_PATH + 'person', {method: 'GET'})
});
export const search = (data, searchTerm) => ({
type: constants.PEOPLE_SEARCH,
payload: _filter(data, searchTerm)
});
export const save = (data) => ({
type: constants.PERSON_SAVE,
payload: request(constants.API_PATH + 'person', {method: 'POST', body: JSON.stringify(data)})
});
This can be an example architecture for your app:
Make a Redux store with list of people.
On initial API call, update the store to contain the list fetched by API call.
Wrap your app inside Provider and pass the store to the Provider.
Use connect and mapStateToProps and mapDispatchToProps to connect the Redux store to React state.
Whenever you update or insert new person, and get the new object, you need to dispatch an action which then goes to the reducer function which finally returns the updated Redux store, and dont worry with the re-rendering as React does the re-rendering itself whenever there is a change in a state.
I'll give a small example of store/actions/reducer, with a react + redux app.
store.js
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from './reducer'
import logger from 'redux-logger'
// TOOD: add middleware
let finalCreateStore = compose(
applyMiddleware(logger())
)(createStore)
export default function configureStore (initialState = { todos: [] }) {
return finalCreateStore(reducer, initialState)
}
actions.js
let actions = {
helloWorld: function(data){
return {
type: 'HELLO_WORLD',
data: data
}
}
};
export default actions
reducer.js // Please read from Redux docs that reducers need to be pure functions
export default function myReducer(state = [], action) {
switch (action.type) {
case 'HELLO_WORLD':
return 'welcome' + data;
default:
return state;
}
}
Component.js (the React App) //In component whenever you receive new object, dispatch an action which will modify the store.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../redux/actions'
class App extends Component {
handleClick() {
store.dispath(action.helloWorld("jimmy")); //this dispatches an action, which goes to the reducer to change the state and adds 'welcome' before 'jimmy'
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
{store.getState()} //getState function to access store values
</div>
)
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch) //binds all the actions with dispatcher and returns them
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
This works like whenever you click the 'div' in the React Component, it calls the function, handleClick(), in which there is an action dispatch. This action then calls the reducer itself to update the store. I know you might get confused that how is store getting updated. Its a bit confusing but for that you need to follow a basic tutorial to explain React+Redux.
Please note this is not a runnable example, just a pseudocode. I recommend you to watch this youtube series to completely understand the redux stores+ react+webpack

EventEmitter and Subscriber ES6 Syntax with React Native

I am trying to implement an EventEmitter/Subscriber relationship between two components in a react native class. I have seen referenced the following materials:
React Native - Event Emitters by Colin Ramsay
React Native - Call Function of child from NavigatorIOS
These solutions are adequate for what I am trying to accomplish, however, they bother require the use of mixins: [Subscribable.Mixin] on the receiving component to work properly with Subscriber. Unfortunately, I am using ES6 and extending my classes from Component so I can not use this mixin syntax.
My question is: How can I implement the above solutions in ES6 without the use of mixins?
You don't need mixins to use EventEmitters.
Simple demo:
import EventEmitter from 'EventEmitter';
let x = new EventEmitter();
function handler(arg) {
console.log(`event-name has occurred! here is the event data arg=${JSON.stringify(arg)}`);
}
x.addListener('event-name', handler);
x.emit('event-name', { es6rules: true, mixinsAreLame: true });
The full signature for addListener takes three args:
EventEmitter.addListener(eventName, handler, handlerContext)
In a react component, you likely want to use that context arg, so that the handler can be a class method instead of an inline function and still retain this == component instance. E.g.:
componentDidMount() {
someEmitter.addListener('awesome', this.handleAwesomeEvents, this);
// the generalist suggests the alternative:
someEmitter.addListener('awesome', this.handleAwesomeEvents.bind(this));
}
handleAwesomeEvents = (event) => {
let awesomeness = event.awesomeRating;
// if you don't provide context in didMount,
// "this" will not refer to the component,
// and this next line will throw
this.setState({ awesomeness });
};
FYI: I got this from looking at the decidedly unmagical implementation of the infamous Subscribable mixin. Google search results are basically an echo chamber of Ramsay's single mixin-based demo.
P.S. As far as exposing this emitter to another component, I'd probably have the owning component provide a function for receiving the emitter reference, and the component that creates the emitter would then conditionally execute that prop with the emitter.
// owner's render method:
<ThingThatEmits
onEmitterReady={(emitter) => this.thingEmitter = emitter}
/>
// inside ThingThatEmits:
componentDidMount() {
this.emitter = new EventEmitter();
if(typeof this.props.onEmitterReady === 'function') {
this.props.onEmitterReady(this.emitter);
}
}
This might be a very late answer, but I'm just going to put it out there for anyone who might find this useful.
As of the time of writing this answer (July, 2020), React Native has changed a lot since version 0.60.0+, you can either use an instance of EventEmitter, or statically call the DeviceEventEmitter methods.
Here is an example using EventEmitter:
import { EventEmitter } from 'events';
const newEvent = new EventEmitter();
// then you can use: "emit", "on", "once", and "off"
newEvent.on('example.event', () => {
// ...
});
Another example using the DeviceEventEmitter:
import { DeviceEventEmitter } from 'react-native';
// then you can directly use: "emit", "addListener", and "removeAllListeners"
DeviceEventEmitter.emit('example.event', ['foo', 'bar', 'baz']);
Hope that comes handy for anyone who still looking for a way to implement custom events in React Native.
I was able to get a workaround with react-mixin. Not sure how proper it is, but it works without any modification. The key is adding reactMixin(DetailView.prototype, Subscribable.Mixin); after the class definition.
Going off the example that is floating around for EventEmitter and Subscribable:
'use strict';
var reactMixin = require('react-mixin');
var React = require('react-native');
var EventEmitter = require('EventEmitter');
var Subscribable = require('Subscribable');
var {
AppRegistry,
StyleSheet,
Text,
View,
NavigatorIOS
} = React;
class MainView extends Component {
constructor(props){
super(props);
this.EventEmitter = new EventEmitter();
}
somethingHappenedFunction(){
this.EventEmitter.emit("update_event", { message: "hello from up here"});
}
//rest of the class
}
class DetailView extends Component {
componentDidMount(){
this.addListenerOn(this.props.events, 'update_event', this.miscFunction);
}
miscFunction(args) {
console.log("message: %s", args.message);
}
//rest of the class
}
reactMixin(DetailView.prototype, Subscribable.Mixin);
With react-native 0.69.0 I solved it like this:
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
const emitter = new EventEmitter();
emitter.addListener('event name', (...args) => console.log('emitted with', args));
emitter.emit('event name', { message: 'Foo' });