React Native net-info - react-native

I'm trying to add an offline notification bar into my app, I have the following code that is called from my App.js.
import React, { PureComponent } from 'react';
import { View, Text, Dimensions, StyleSheet,Alert } from 'react-native';
import NetInfo from "#react-native-community/netinfo";
const dimensions = Dimensions.get('window');
let outofaction = 1;
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
if (state.isConnected == false) {
outofaction = 0;
} else {
outofaction = 1;
}
});
//class OfflineNotice extends PureComponent {
const OfflineNotice = () => {
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
if (state.isConnected == false) {
outofaction = 0;
} else {
outofaction = 1;
}
});
// Subscribe
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
if (state.isConnected == false) {
outofaction = 0;
} else {
outofaction = 1;
}
});
// Unsubscribe
unsubscribe();
function MiniOfflineSign() {
if (outofaction == 0) {
return (
<View style={styles.offlineContainer}>
<Text style={styles.offlineText}>Offline</Text>
</View>
);
} else {
return (
<View style={styles.offlineContainer}>
<Text style={styles.offlineText}>Online</Text>
</View>
);
}
}
return (
<MiniOfflineSign />
)};
const styles = StyleSheet.create({
offlineContainer: {
backgroundColor: '#b52424',
height: 30,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
width: dimensions.width,
position: 'absolute',
top:40,
zIndex:1
},
offlineText: { color: '#fff' }
});
export default OfflineNotice;
The code works partially. I start Online, then turn off my internet on my laptop, and if I refresh / reload, then it will show offline.
Two problems I have;
I want it to update in near real-time when the isConnected changes (this doesn't appear to be happening)
It doesn't get stuck on one state (though item 1 above would fix that)

I made something similar, this might help you with things.
NoInternetMessageBar component
// NoInternetMessageBar.js
import Netinfo from '#react-native-community/netinfo';
import React, { useContext, useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Store } from 'store/Store';
export default function NoInternetMessageBar() {
const {
state,
actions: { networkConnectionChanged },
} = useContext(Store);
useEffect(() => {
const unsubscribe = Netinfo.addEventListener(({ isConnected }) => networkConnectionChanged(isConnected));
return () => {
unsubscribe();
};
}, []);
if (state.isConnected) return null;
return (
<View style={styles.container}>
<Text style={styles.message}>Cannot reach internet</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
position: 'absolute',
bottom: 0,
height: 40,
width: '100%',
backgroundColor: 'gray',
},
message: {
color: 'white',
marginLeft: 20,
},
});
action
const networkConnectionChanged = async isConnected => {
return dispatch({
type: NETWORK_CHANGED,
payload: isConnected,
});
};
reducer
case NETWORK_CHANGED:
return {
...state,
isConnected: action.payload,
};
On top level use it like this.
<>
<AppContainer />
<NoInternetMessageBar />
</>

You are unsubscribing from NetInfo updates immediately after subscribing to them. Functional components are just that - functions. The code you wrote in there will be executed every time that component renders.
You should instead put your subscribe/unsubscribe in a useEffect hook, so that you subscribe on mount and unsubscribe on unmount.
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
...
});
return () => {
unsubscribe();
}
}, []);

Subscribe in componentDidMount() and Unsubscribe in componentWillUnmount(), like this -
class A extends React.Component {
constructor() {
this.unsubscribe;
}
componentDidMount(){
this.unsubscribe = NetInfo.addEventListener(...);
}
componentWillUnmount() {
this.unsubscribe && this.unsubscribe();
}
}

Related

React Native Modal not displaying using context and redux

I created a custom ModalContext.js but my test modal does not seem to be showing when I trigger a button's onPress
// ModalContext.js
import createDataContext from './createDataContext';
const modalReducer = (state, action) => {
switch (action.type) {
case 'openModal':
return { ...state, component: action.payload };
case 'hideModal':
return { ...state, component: null };
default:
return state;
}
};
const openModal = (dispatch) => (component) => {
console.log('hey there');
dispatch({ type: 'openModal', payload: component });
};
const hideModal = (dispatch) => () => {
dispatch({ type: 'hideModal' });
};
export const { Provider, Context } = createDataContext(
modalReducer,
{
openModal,
hideModal,
},
{ component: null }
);
// createDataContext.js
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
// App.js
const App = createAppContainer(navigator);
export default () => {
return (
<ModalProvider>
<AuthProvider>
<App
ref={(navigator) => {
setNavigator(navigator);
}}
/>
</AuthProvider>
</ModalProvider>
);
};
I have a button on a test screen to check if it works or not.
// WebViewScreen.js
import React, { useState, useContext } from 'react';
import { StyleSheet, Modal, View, Text, Button } from 'react-native';
import { Context as ModalContext } from '../context/ModalContext';
const WebViewScreen = ({ navigation }) => {
const { state, openModal } = useContext(ModalContext);
const errorModal = (
<View>
<Modal animationType='slide' visible={true}>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text>Hello</Text>
</View>
</View>
</Modal>
</View>
);
return (
<>
<Button
onPress={() => {
openModal(errorModal);
}}
title='button'
/>
</>
);
};
WebViewScreen.navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('title'),
};
};
const styles = StyleSheet.create({
view: {
backgroundColor: '#f1f3f4',
},
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
margin: 20,
backgroundColor: 'white',
borderRadius: 20,
padding: 35,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
});
export default WebViewScreen;
It seems to actually call the function as I can see "hey there" on the console but no modals appear.
I am not sure this is what you are looking for or not but I manage my details like following.
class Abc {
var1;
var2;
var3 = new B();
var4 = new C();
var5 = {}
var6 = [];
success = a => {
//your logic
}
}
class B {
varA;
varB;
varC = false;
}
I hope this is helpful.

Action dispatched and state updated, but no render of the UI component

Who can support on my issue: Dispatching an action does does change the state as anticipated. But the component from which the issue gets dispatched does not re-render. When I simply save the component, it of course re-renders and shows the desiree effekt.
Here my files:
actions.js
export const TOGGLE_PRODUCT = "TOGGLE_PRODUCT";
export const INCREMENT = "INCREMENT";
//ACTION CREATER FUNCTIONS
export const toggleProduct = (id) => {
return {
type: TOGGLE_PRODUCT,
productId: id,
};
};
reducer.js
import { PRODUCTLIST } from "../../data/dummydata";
import { TOGGLE_PRODUCT } from "../actions/products";
const initialState = {
allProducts: PRODUCTLIST,
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_PRODUCT:
const Products = state.allProducts;
const toggledProduct = Products.find((el) => el.id === action.productId);
if (toggledProduct.status === false) {
toggledProduct.status = true;
} else {
toggledProduct.status = false;
}
console.log("Neue Products: ", Products);
return {
allProducts: Products,
};
default:
return state;
}
};
export default productReducer;
component.js
import { useSelector, useDispatch } from "react-redux";
import React, { useEffect, useCallback } from "react";
import { Text, View, Button, FlatList, StyleSheet } from "react-native";
import Product from "../components/Product";
import { toggleProduct } from "../store/actions/products";
import { increment } from "../store/actions/products";
const ShoppingListScreen = (props) => {
const dispatch = useDispatch();
const toggleProductHandler = useCallback(
// useCallback verhindert infinite loop
(id) => {
dispatch(toggleProduct(id));
},
[]
);
const Products = useSelector((state) => state.product.allProducts);
return (
<View style={styles.screen}>
<FlatList
data={Products}
renderItem={({ item }) => (
<View
style={
item.status === true ? styles.elementselected : styles.element
}
>
<Product
style={styles.text}
id={item.id}
product={item.product}
department={item.department}
status={item.status}
onClick={() => toggleProductHandler(item.id)}
/>
</View>
)}
/>
<View style={styles.button}>
<Button
title="FERTIG"
onPress={() => {
props.navigation.goBack();
}}
/>
{/* <Button
title='Stand "cartRewe" '
onPress={() => {
props.testFunction1();
}}
/>
<Button
title='Stand "planRewe" '
onPress={() => {
props.testFunction2();
}}
/> */}
</View>
</View>
);
};
const styles = StyleSheet.create({
screen: {
backgroundColor: "#fafafa",
flex: 1,
justifyContent: "flex-start",
},
element: {
backgroundColor: "#ddd",
borderWidth: 2,
borderColor: "#bbb",
borderRadius: 20,
marginVertical: 5,
marginHorizontal: 25,
},
elementselected: {
backgroundColor: "#a0ffa0",
borderWidth: 3,
borderColor: "#64ff64",
borderRadius: 20,
marginVertical: 5,
marginHorizontal: 25,
},
text: {
color: "#333",
// fontSize: 22,
// marginHorizontal: 10
},
button: {
marginVertical: 24,
},
});
export default ShoppingListScreen;
These lines are the problem:
const Products = state.allProducts;
const toggledProduct = Products.find((el) => el.id === action.productId);
if (toggledProduct.status === false) {
toggledProduct.status = true;
} else {
toggledProduct.status = false;
}
return {
allProducts: Products,
};
That's mutating the existing state, and you must never mutate the state in a Redux reducer!.
To fix this, you would need to make copies of the Products array and the toggledProduct object, update the copies, and return those.
Having said that, the better option is to use our official Redux Toolkit package, which is our recommended approach for writing Redux logic. It lets you write "mutating" logic that is turned into safe and correct immutable updates.
Here's what this would look like with Redux Toolkit's createSlice API:
const productsSlice = createSlice({
name: 'products',
initialState: {allProducts: []},
reducers: {
productToggled(state, action) {
const toggledProduct = state.allProducts.find(e => e.id === action.payload);
// This "mutating" syntax _only_ works inside Redux Toolkit's createSlice/createReducer!
toggledProduct.status = !toggledProduct.status;
}
}
})
This totally makes sense to not mutate the state object. Since I do now want to implement a further package just for this issue, I revised my source code as advised. For any reason my component still does not rerender without a manual safe. How about this code, what is still wrong?
reducer.js
import { PRODUCTLIST } from "../../data/dummydata";
import { TOGGLE_PRODUCT } from "../actions/products";
const initialState = {
allProducts: PRODUCTLIST,
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_PRODUCT:
const newProducts = state.allProducts;
const toggledProduct = newProducts.findIndex(
(el, idx) => el.id === action.productId
);
if (newProducts[toggledProduct].status === false) {
newProducts[toggledProduct].status = true;
} else {
newProducts[toggledProduct].status = false;
}
return {
...state,
newProducts,
};
default:
return state;
}
};
export default productReducer;

Could not get React-Native-WebRTC to work

Need some help running through my codes to make WebRTC work. I am just trying to make a call from one device to the other. Realise I'm not throughly understanding WebRTC. My app crashes when the answer is emitted back.
The socket/emitter I am working with is actually
FeathersJS services. It is serving as my backend, whenever I need to emit, I send a create service and in my backend, feathersJS emit a customEmitter ['offer' 'answer' 'ice-candidate']. Although I am able to receive all emitted events, I am unable to get WebRTC to work. The below are my React-native code:
import React, { useContext, useEffect, useState } from 'react';
import {
View, SafeAreaView, Button, StyleSheet,
} from 'react-native';
import {
RTCPeerConnection,
RTCSessionDescription,
RTCView,
mediaDevices,
} from 'react-native-webrtc';
import ContextApp from '../../context/contextApp';
import FeathersApp, { Services } from '../../Feathers';
const styles = StyleSheet.create({
container: {
backgroundColor: '#313131',
justifyContent: 'space-between',
alignItems: 'center',
height: '100%',
},
text: {
fontSize: 30,
},
rtcview: {
justifyContent: 'center',
alignItems: 'center',
height: '40%',
width: '80%',
backgroundColor: 'black',
},
rtc: {
width: '80%',
height: '100%',
},
toggleButtons: {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
},
});
const VideoCallPage = ( props: any ) => {
const { userId } = props;
const { authState } = useContext(ContextApp);
const callService = FeathersApp.service(Services.CALL);
const [callAlert, setCallAlert] = useState<boolean>(false);
const [offerData, setOfferData] = useState<any>();
const configuration = {"iceServers": [{"url": "stun:stun.l.google.com:19302"}]};
const localPC = new RTCPeerConnection(configuration);
const [localStream, setLocalStream] = useState<any>();
const [remoteStream, setRemoteStream] = useState<any>();
const sendToPeer = (messageType: any, payload: any) => {
callService.create({
callType: messageType,
sdp: payload,
caller: authState._id,
answerer: userId,
candidate: payload,
})
}
const createOffer = async () => {
const offer = await localPC.createOffer();
await localPC.setLocalDescription(offer).then(() => console.log('local offer succeeded'));
sendToPeer('offer', localPC.localDescription);
}
const createIceCandidate = () => {
localPC.onicecandidate = e => {
try {
console.log('localPC icecandidate:', e.candidate);
if (e.candidate) {
sendToPeer('ice-candidate', e.candidate)
}
} catch (err) {
console.error(`Error adding remotePC iceCandidate: ${err}`);
}
};
}
const startCall = async () => {
console.log('start call funtion')
const newStream = await mediaDevices.getUserMedia({
audio: true,
video: false,
});
setLocalStream(newStream);
createIceCandidate()
createOffer();
};
const recieveCall = async (incoming: any) => {
console.log('accepting call');
await localPC.setRemoteDescription(new RTCSessionDescription(incoming.sdp));
const answer = await localPC.createAnswer();
await localPC.setLocalDescription(answer).then(() => console.log('localPC desc succeeded'));
sendToPeer('answer', localPC.localDescription);
createIceCandidate();
localPC.addStream(localStream);
localPC.onaddstream = e => {
console.log('remotePC tracking with ', e);
if (e.stream && remoteStream !== e.stream) {
console.log('RemotePC received the stream', e.stream);
setRemoteStream(e.stream);
}
};
};
const recieveAnswer = (payload: any) => {
console.log('recieved answer: ', payload.candidate.sdp)
localPC.setRemoteDescription(new RTCSessionDescription(payload.candidate.sdp));
localPC.onaddstream = e => {
if (e.stream && remoteStream !== e.stream) {
console.log('RemotePC received the stream', e.stream);
setRemoteStream(e.stream);
}
};
}
// alert for any calls
useEffect(() => {
callService.on('ice-candidate', (incoming: any) => {
localPC.addIceCandidate(incoming.data.sdp);
localPC.addStream(localStream);
});
callService.on('offer', async (payload: any) => {
if (payload.data.answerer === authState._id) {
const newStreamForReciever = await mediaDevices.getUserMedia({
audio: true,
video: false,
});
setLocalStream(newStreamForReciever);
setOfferData(await payload.data);
setCallAlert(true);
}
});
callService.on('answer', async (payload: any) => {
if (payload.data.answerer === authState._id) {
console.log(userId, 'answered')
recieveAnswer(await payload.data);
}
});
// return (() => {
// callService.removeAllListener('ice-candidate');
// callService.removeAllListener('offer');
// callService.removeAllListener('answer');
// })
}, [callService]);
return (
<SafeAreaView style={styles.container}>
{!localStream && <Button title="Click to start CALL stream" onPress={startCall} />}
{callAlert && <Button title="Answer Call" onPress={() => recieveCall(offerData)} />}
{localStream && (
<View style={styles.toggleButtons}>
</View>
)}
<View style={styles.rtcview}>
{localStream && <RTCView style={styles.rtc} streamURL={localStream.toURL()} />}
</View>
<View style={styles.rtcview}>
{remoteStream && <RTCView style={styles.rtc} streamURL={remoteStream.toURL()} />}
</View>
</SafeAreaView>
)
};
export default VideoCallPage;
What is it that I am missing out??
Any guidance you can provide is greatly appreciated!

Expo Signature - Crash onChange content

I wrote this code for get the signature of a person:
import * as ExpoPixi from 'expo-pixi';
import React, { Component } from 'react';
import { Platform, AppState, StyleSheet, Text, View } from 'react-native';
const isAndroid = Platform.OS === 'android';
function uuidv4() {
// https://stackoverflow.com/a/2117523/4047926
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export default class App extends Component {
state = {
signature: null,
appState: AppState.currentState,
};
handleAppStateChangeAsync = nextAppState => {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
if (isAndroid && this.sketch) {
this.setState({ appState: nextAppState, id: uuidv4(), lines: this.sketch.lines });
return;
}
}
this.setState({ appState: nextAppState });
};
componentDidMount() {
AppState.addEventListener('change', this.handleAppStateChangeAsync);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChangeAsync);
}
onChange = async () => {
const { uri } = await this.sketch.takeSnapshotAsync();
this.setState({
signature: { uri },
}, () => console.log(this.state.signature));
}
render() {
return (
<View style={{flex: 1, backgroundColor: 'white'}}>
<View style={{flex: 1, left: '5%'}}>
<ExpoPixi.Signature
ref={signature => (this.sketch = signature)}
style={styles.pad}
strokeColor={'black'}
strokeAlpha={0.5}
onChange={this.onChange}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
pad: {
flex: 1,
width: '90%',
borderWidth: 0.6,
borderColor: '#b3b3b5',
borderRadius: 10,
backgroundColor: 'white'
},
});
Running this simple code that is inspired on this library example, I notice that the borderRadius is ignored (the corner of the View are missing) and when I try to write the signature the app crash.
A side note: is it possible to get a base64-encoded version of the pad content instead of the uri?

Netinfo connectionChange event listener not getting detached

I have added connectionChange event listener on splash screen componentDidMount, but it is not removed on componentwillUnmount. It is active on every page of the application. How can I detach it on componentWillUnmount.
componentDidMount() {
NetInfo.addEventListener('connectionChange',
(networkType)=> {
this.handleFirstConnectivityChange({networkType})
}
}
componentWillUnmount() {
this.notificationListener.remove();
NetInfo.removeEventListener(
'connectionChange'
);
}
You need to pass the same callback you used in addEventListener to removeEventListener:
class SomeClass extends Component {
handleConnectivityChange = networkType => {
//...
};
componentDidMount() {
NetInfo.addEventListener(
"connectionChange",
this.handleConnectivityChange
);
}
componentWillUnmount() {
NetInfo.removeEventListener(
"connectionChange",
this.handleConnectivityChange
);
}
}
Note that you shouldn't create a new arrow function wrapper when calling addEventListener, because you won't have a reference to that function instance, and you can't pass it to removeEventListener to unregister it. Instead, define the callback on the class instance, as above.
import OfflineNotice anywhere this works fine and please note u can see this working only in a physical device, not in the simulator
import React, { useEffect } from 'react';
import { View, Text, Dimensions, StyleSheet } from 'react-native';
import NetInfo from "#react-native-community/netinfo";
const { width } = Dimensions.get('window');
function MiniOfflineSign() {
return (
<View style={styles.offlineContainer}>
<Text style={styles.offlineText}>No Internet Connection</Text>
</View>
);
}
let currentNetwork;
NetInfo.fetch().then(state => {
currentNetwork = state.isConnected;
});
const OfflineNotice = () => {
const [isConnected, setConnected] = React.useState(currentNetwork);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Is connected?", state.isConnected);
setConnected(state.isConnected);
});
return unsubscribe
}, [])
return (
<>
{!isConnected && (<MiniOfflineSign />)}
</>
)
}
const styles = StyleSheet.create({
offlineContainer: {
backgroundColor: '#b52424',
height: 30,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
width,
position: 'absolute',
top: 30
},
offlineText: { color: '#fff' }
});
export default OfflineNotice;