React Native redux using connect and store in the same component - react-native

I'm working on a react native app, i use redux to manage global state.
I want to store a state in the app component where i call the store :
/* eslint-disable global-require */
import React, { Component } from 'react'
import { Provider, connect } from 'react-redux'
import { BackHandler } from 'react-native'
// ***************************************************
import AppContainer from './eXpand/Components/Navigation';
// ***************************************************
import { AppLoading } from 'expo'
import * as Font from 'expo-font'
import { Ionicons } from '#expo/vector-icons'
import {
cacheAssets,
cacheFonts
} from './eXpand/Helpers/Defaults/AssetsCaching'
import store from '~/Store/store'
import registerForPushNotificationsAsync from './eXpand/Components/Services/notifications';
// Local Import
import { setUserGsm } from '~/Store/actions';
class App extends Component {
_isMounted = false;
constructor(props) {
super(props)
this.state = {
isReady: false,
GSM: null
}
}
/**
* Demande d'autorisation pour accéder au token du GSM et l'envoyer vers l'api
*/
async registerForPush() {
const { status: existingStatus } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
finalStatus = status;
}
if (finalStatus !== 'granted') {
return;
}
let gsm = await Notifications.getExpoPushTokenAsync();
if (this._isMounted) {
this.setState({
GSM: gsm
});
}
console.log("### GSM TOKEN GSM ###")
console.log(this.state.GSM)
console.log("#####################")
}
/**
* Add Font with Asynchronous Method
*/
async componentDidMount() {
console.log("########## COMPONENT DID MOUNT ############")
this._isMounted = true;
if (this._isMounted) {
this.registerForPush()
this.props.setUserGsm(this.state.GSM)
}
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
await Font.loadAsync({
// ROBOTO
Roboto: require('native-base/Fonts/Roboto.ttf'),
Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'),
// SFUIDisplay
SFUIDisplayBlack: require('^/Fonts/SFUIDisplay-Black.otf'),
SFUIDisplayBold: require('^/Fonts/SFUIDisplay-Bold.otf'),
SFUIDisplayHeavy: require('^/Fonts/SFUIDisplay-Heavy.otf'),
SFUIDisplayLight: require('^/Fonts/SFUIDisplay-Light.otf'),
SFUIDisplayMedium: require('^/Fonts/SFUIDisplay-Medium.otf'),
SFUIDisplaySemibold: require('^/Fonts/SFUIDisplay-Semibold.otf'),
SFUIDisplayThin: require('^/Fonts/SFUIDisplay-Thin.otf'),
SFUIDisplayUltralight: require('^/Fonts/SFUIDisplay-Ultralight.otf'),
// MyriadPro
MYRIADPROBOLD: require('^/Fonts/MyriadPro-Bold.otf'),
MyriadProBlackSemiCn: require('^/Fonts/MyriadPro-BlackSemiCn.otf'),
MyriadProBoldSemiExtended: require('^/Fonts/MyriadPro-BoldSemiExtended.ttf'),
...Ionicons.font,
// PTSans
PTSansRegular: require('^/Fonts/PTSans-Regular.ttf'),
PTSansBold: require('^/Fonts/PTSans-Bold.ttf'),
PTSansItalic: require('^/Fonts/PTSans-Italic.ttf'),
})
this.setState({ isReady: true })
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
console.log("########## COMPONENT WILL MOUNT ############")
this._isMounted = false;
}
handleBackButton() {
return true;
}
async _loadAssetsAsync() {
const imageAssets = cacheAssets([require('^/Logos/expanded.png')])
const fontAssets = cacheFonts({
// ROBOTO
Roboto: require('native-base/Fonts/Roboto.ttf'),
Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'),
// SFUIDisplay
SFUIDisplayBlack: require('^/Fonts/SFUIDisplay-Black.otf'),
SFUIDisplayBold: require('^/Fonts/SFUIDisplay-Bold.otf'),
SFUIDisplayHeavy: require('^/Fonts/SFUIDisplay-Heavy.otf'),
SFUIDisplayLight: require('^/Fonts/SFUIDisplay-Light.otf'),
SFUIDisplayMedium: require('^/Fonts/SFUIDisplay-Medium.otf'),
SFUIDisplaySemibold: require('^/Fonts/SFUIDisplay-Semibold.otf'),
SFUIDisplayThin: require('^/Fonts/SFUIDisplay-Thin.otf'),
SFUIDisplayUltralight: require('^/Fonts/SFUIDisplay-Ultralight.otf'),
// MyriadPro
MYRIADPROBOLD: require('^/Fonts/MyriadPro-Bold.otf'),
MyriadProBlackSemiCn: require('^/Fonts/MyriadPro-BlackSemiCn.otf'),
MyriadProBoldSemiExtended: require('^/Fonts/MyriadPro-BoldSemiExtended.ttf'),
// PTSans
PTSansRegular: require('^/Fonts/PTSans-Regular.ttf'),
PTSansBold: require('^/Fonts/PTSans-Bold.ttf'),
PTSansItalic: require('^/Fonts/PTSans-Italic.ttf'),
})
await Promise.all([imageAssets, fontAssets])
}
render() {
const Root = () => {
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._loadAssetsAsync}
onFinish={() => this.setState({ isReady: true })}
/>
)
}
return <AppContainer />
}
return (
<Provider store={store}>
<Root />
</Provider>
)
}
}
const mapStateToProps = (state) => ({
GSM: state.GSM
});
const mapDispatchToProps = (dispatch) => ({
setUserGsm: (GSM) => {
dispatch(setUserGsm(GSM));
}
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(App);
I get this error :
Invariant Violation: Could not find "store" in the context of "Connect(App)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(App) in connect options.

You cannot do it this way.
The react-redux Provider is passing store to the react-redux connect. And you are using connect in component which is not wrapped (somewhere in React component tree) in Provider (the your component itself is rendering Provider, which is too late).

Related

Accessing push notification data while app is killed

I am trying to access push notification data with killed app.
I have been reading the docs and implemented the steps that are provided but still i don't get any data, it's like the push notification is not detected.
I am using:
"expo": "^42.0.0",
"expo-notifications": "~0.12.3",
The code is the following:
import { enableScreens } from 'react-native-screens'
import { NavigationContainer } from '#react-navigation/native';
import CarNavigator from './src/navigation/CarNavigator'
import { Alert } from 'react-native';
import React, { useState, useEffect, useRef } from 'react'
import * as Notifications from 'expo-notifications'
import Constants from 'expo-constants';
enableScreens();
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export default function App() {
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
const registerForPushNotificationsAsync = async () => {
let token;
if (Constants.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
console.log('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
} else {
console.log('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
Alert.alert('IN');
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
/* END */
return (
<NavigationContainer>
<CarNavigator />
</NavigationContainer>
)
}
As per the documentation this part handles push notification data while app is killed as stated here:
https://docs.expo.dev/versions/latest/sdk/notifications/#listening-to-notification-events
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
Alert.alert('IN');
});
When i send a push notification and start the app using that notification the Alert is not triggered, it's like the push notification is not detected.
I am testing this on android with a standalone apk build.
Anyone had this issue and managed to solve it?
Many thanks,
Trix

TypeError: undefined is not a function (near '..._fire.default.get...')

When I try to enter username and then go on next screen for live chating then I facing this error.
Here is code for ChatScreen.js file.
TypeError: undefined is not a function (near '..._fire.default.get...').
ChatScreen.js
import React,{Component} from "react";
import {Platform,KeyboardAvoidingView} from 'react-native';
import {GiftedChat}from 'react-native-gifted-chat-fix';
import{SafeAreaView}from 'react-native-safe-area-view';
import Video from 'react-native-video';
import Fire from '../fire';
export default class ChatScreen extends Component{
state={
messages:[]
}
get user(){
return{
_id:Fire.uid,
name:this.props.navigation.state.params.name
}
}
componentDidMount(){
Fire.get(message=>this.setState(previous=>({
messages:GiftedChat.append(previous.messages,message)
}))
);
}
componentWillUnmount(){
Fire.off()
}
render(){
const chat=<GiftedChat messages={this.state.messages} onSend={Fire.send} user={this.user}/>;
if(Platform.OS=='android'){
return(
<KeyboardAvoidingView style={{flex:1}}behavior="padding" keyboardVerticalOffset={30} enabled>
{chat}
</KeyboardAvoidingView>
);
}
return<SafeAreaView style={{flex:1}}>{chat}</SafeAreaView>;
}
}
Try changing the code in both files
At first in Fire.js
import firebase from 'firebase'; // 4.8.1
class Fire {
constructor() {
this.init();
this.observeAuth();
}
init = () => {
if (!firebase.apps.length) {
firebase.initializeApp({
apiKey:'AIzaSyAPfes9_2EwZESX1puYMUv29yunzK9Ve5U',
authDomain:'docman-31d96.firebaseapp.com',
databaseURL: "https://docman-31d96.firebaseio.com",
projectId: "docman-31d96",
storageBucket: "docman-31d96.appspot.com",
messagingSenderId: "649332068608",
appId:'1:649332068608:android:08c080ee6a4e521f5323e5'
});
}
};
observeAuth = () =>
firebase.auth().onAuthStateChanged(this.onAuthStateChanged);
onAuthStateChanged = user => {
if (!user) {
try {
firebase.auth().signInAnonymously();
} catch ({ message }) {
alert(message);
}
}
};
get uid() {
return (firebase.auth().currentUser || {}).uid;
}
get ref() {
return firebase.database().ref('messages');
}
parse = snapshot => {
const { timestamp: numberStamp, text, user } = snapshot.val();
const { key: _id } = snapshot;
const timestamp = new Date(numberStamp);
const message = {
_id,
timestamp,
text,
user,
};
return message;
};
on = callback =>
this.ref
.limitToLast(20)
.on('child_added', snapshot => callback(this.parse(snapshot)));
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
// send the message to the Backend
send = messages => {
for (let i = 0; i < messages.length; i++) {
const { text, user } = messages[i];
const message = {
text,
user,
timestamp: this.timestamp,
};
this.append(message);
}
};
append = message => this.ref.push(message);
// close the connection to the Backend
off() {
this.ref.off();
}
}
Fire.shared = new Fire();
export default Fire;
and then in ChatScreen.js
import * as React from 'react';
import { Platform , KeyboardAvoidingView,SafeAreaView } from 'react-native';
// #flow
import { GiftedChat } from 'react-native-gifted-chat'; // 0.3.0
import Fire from '../fire';
type Props = {
name?: string,
};
class ChatScreen extends React.Component<Props> {
static navigationOptions = ({ navigation }) => ({
title: (navigation.state.params || {}).name || 'Chat!',
});
state = {
messages: [],
};
get user() {
return {
name: this.props.navigation.state.params.name,
_id: Fire.shared.uid,
};
}
render() {
const chat=<GiftedChat messages={this.state.messages} onSend={Fire.shared.send} user={this.user}/>;
if(Platform.OS=='android'){
return(
<KeyboardAvoidingView style={{flex:1}}behavior="padding" keyboardVerticalOffset={0} enabled>
{chat}
</KeyboardAvoidingView>
);
}
return<SafeAreaView style={{flex:1}}>{chat}</SafeAreaView>;
}
componentDidMount() {
Fire.shared.on(message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message),
}))
);
}
componentWillUnmount() {
Fire.shared.off();
}
}
export default ChatScreen;
This helped for me It should work for you too
To see my chat app just visit https://snack.expo.io/#habibishaikh1/chatapp

Handling Errors from Redux API Call as a Toast

So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?

mapStateToProps does not re-render the component after state change

I've been working with React/React-Native for a while now, but I'm new to Redux and I cannot find the problem. I have a RESTFull API and two main modules: the service model and the price model. Once the admin user adds a new service the user can also associate a price for that service. The problema is that when I add a service (in the NewServiceScreen) my code dispatches an action to change the redux store and therefore update the service list on the NewPriceScreen for the user to associate a price with the service that was just added.
NewPriceScreen.js
function mapStateToProps(state){
return {
newPrice: state.newPriceReducer
}
}
// Exports the connected NewPriceScreen
export default connect(mapStateToProps)(NewPriceScreen);
NewServiceScreen
...
handleSubmit = async() => {
const { descricao } = this.props.newService;
const userToken = await AsyncStorage.getItem('token');
axios.post('/estabelecimento/servicos/',{descricao: descricao}, {
headers: {
"Authorization": `Token ${userToken}`
}
})
.then(res => {
Alert.alert(
'Deu tudo certo :)',
'Dados salvos com sucesso !',
);
console.log(res.data);
this.props.dispatch({type: 'addService', newService: res.data});
})
.catch(error =>{
Alert.alert(
'Ops ! Algo aconteceu :(',
error.message,
);
})
}
...
const mapStateToProps = state => ({
newService: state.newService
});
export default connect(mapStateToProps)(NewServiceScreen);
Reducers.js
const initialState = {
servicos: [],
// Database variables
servico: 0,
duracao: '',
custo: '',
comissao: ''
}
export default function newPriceReducer(state = initialState, action){
// console.log("My state")
// console.log(state);
switch(action.type){
case 'setState': {
return {
...state,
servico: action.descricao,
duracao: action.duracao,
custo: action.custo,
comissao: action.comissao
}
}
case "ADD_SERVICES": {
// console.log("Servicos");
const newState = {
...state,
servicos: action.servicos
}
// console.log(newState);
// console.log(newState === initialState)
return newState;
}
case 'addService': {
// console.log("ADD Service");
servicos = state.servicos;
servicos.push(action.newService);
const newState = {
...state,
servicos: servicos
}
// console.log(newState);
// console.log(newState === initialState);
return newState
}
default:
return state;
}
}
const initialState = {
descricao: ''
}
export default function newServiceReducer(state = initialState, action){
switch(action.type){
case 'setDescricao': {
return {
...state,
descricao: action.descricao
}
}
default:
return state;
}
}
App.js
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunkMiddleware from 'redux-thunk'
import newServiceReducer from '../reducers/NewService';
import newPriceReducer from "../reducers/NewPrice";
import logger from 'redux-logger';
const mainReducer = combineReducers({
newService: newServiceReducer,
newPriceReducer
})
const store = createStore(mainReducer, applyMiddleware(logger));
export default store
import React from 'react';
import { Platform, StatusBar, StyleSheet, View, AsyncStorage, ImageBackground} from 'react-native';
import { AppLoading, Asset, Font, Icon } from 'expo';
import AppNavigator from './navigation/AppNavigator';
// Redux Stuff
import {Provider} from 'react-redux';
import AppStore from './store/App';
export default class App extends React.Component {
state = {
isLoadingComplete: false,
isLoggedIn: false,
};
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<Provider store={AppStore}>
<ImageBackground source={require('./assets/images/back.png')} style={{width: '100%', height: '100%'}}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</ImageBackground>
</Provider>
);
}
}
}
In lines:
servicos = state.servicos;
servicos.push(action.newService);
I was making some sort mutation. I just changed it to:
const newState = {
...state,
servicos: [...state.servicos, action.newService]
}

React-Native Flash-Message with Mobx and React-navigation

I'm trying to use react-native-flash-message to provide little toasts in my app, and I am supposed to add the flash message to my root view. I cannot figure out where that is in my app. I used examples to get up and running with mobx/react-navigation/react-native, and so my app.js actually just exports an index.js that looks like this:
import React from 'react';
import { createRootNavigator } from './router';
import { isSignedIn } from './auth';
import { Font, SplashScreen } from 'expo';
import { library } from '#fortawesome/fontawesome-svg-core';
import { faCheckSquare, faCoffee, faHome } from '#fortawesome/free-solid-svg-icons';
import { configure } from 'mobx';
import { Provider } from 'mobx-react';
import _ from 'lodash';
import { RootStore } from './stores/RootStore';
import { getDecoratedStores } from './stores/util/store-decorator';
import { AsyncStorage } from 'react-native';
import FlashMessage from "react-native-flash-message";
configure({ enforceActions: 'observed' });
const rootStore = new RootStore();
const stores = getDecoratedStores(rootStore);
//Library of Icons
library.add(faCheckSquare, faCoffee, faHome);
export default class App extends React.Component<
{},
{ checkedSignIn: boolean; signedIn: boolean; loaded: boolean }
> {
constructor(props: any) {
super(props);
this.state = {
signedIn: false,
checkedSignIn: false,
loaded: false,
};
}
componentWillMount() {
this._loadFontsAsync();
}
_loadFontsAsync = async () => {
await Font.loadAsync({ robotoBold: require('../app/uiComponents/fonts/Roboto-Bold.ttf') });
await Font.loadAsync({
robotoRegular: require('../app/uiComponents/fonts/Roboto-Regular.ttf'),
});
this.setState({ loaded: true });
};
componentDidMount() {
isSignedIn()
.then(res => {
SplashScreen.hide();
this.setState({ signedIn: res as boolean, checkedSignIn: true });
})
.catch(err => {console.log(err); alert('Error')});
}
render() {
const { checkedSignIn, signedIn } = this.state;
// If we haven't checked AsyncStorage yet, don't render anything (better ways to do this)
if (!checkedSignIn) {
return null;
}
AsyncStorage.getItem('auth-demo-key').then((data) => console.log("Async Storage: " + data));
const Layout = createRootNavigator(signedIn);
return (
<Provider {...stores}>
<Layout />
</Provider>
);
}
}
Can anyone help me figure out how to add my flash message in this return statement? I tried wrapping the provider in a view, but that failed and crashed my app, same with adding it within the provider (a view), also tried just adding flash message here in the provider, but that failed, too. Can anyone help?