How to check wifi status on change from Off to on - react-native

i want to use service in react-native using native modules ,if Wifi connectivity change from off to on just show toast that connection change

You can use react-native-netinfo plugin. Install this plugin and then add below code to your file :
import NetInfo from '#react-native-community/netinfo';
...
...
class App extends Component {
netInfoHandler;
constructor(props) {
super(props);
this.state = {
isConnected: true
}
}
componentDidMount = () => {
this.netInfoHandler = NetInfo.addEventListener(async (state) => {
if (this.state.isConnected !== state.isConnected) {
this.setState({ isConnected: state.isConnected });
// show toast here
}
});
}
componentWillUnmount = () => {
this.netInfoHandler();
}
render() {
return (
...
// your view
)
}
}
export default App

Related

'Warning: Can\'t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application

im building a react native application ,still i have 2 screens
1.Enter mobile
2.Verify Otp
EnterUserInfo.js
class UserInfoInput extends Component {
constructor(props) {
super(props);
this.state = { formValid:true,
validMobileNumber:false,
.
.}}
componentWillReceiveProps(nextProps) {
if(nextProps.common.isFetching===false) {
this.props.navigation.navigate('VerifyOtpScreen')
.
.
} else {
this.setState({isLoading:true})
}}
onPressNext=()=> {
this.props.sendOtp(payload)}
render() {
return (<View/>)
}
}
}
function mapStateToProps(state) {
return {
common: state.common
}
}
function mapDispatchToProps(dispatch) {
return {
...bindActionCreators({ sendOtp }, dispatch)
}
}
export default connect(mapStateToProps,mapDispatchToProps)(UserInfoInput);
Here user enter the phone number ,and trigger an action sendOtp,response will be in the reducer and it will be available in the componentwillrecieveprops() lifecycle.
VerifyOtp.js
class VerifyOtp extends Component {
constructor(props) {
super(props);
this.state = { oneTimePIN: '' ,
.};
}
componentDidMount(){
this.setState({ phoneNumber:this.props.common.phone});
}
componentWillMount() {
setTimeout(() => {
this.setState({ isResendDisabled: false, opacity: 1 });
}, 30000);
}
componentWillReceiveProps(nextProps){
//do operation
}
onPressNext=()=>{
if(this.state.oneTimePIN=='') {
this.setState({showNotification:true})
}
else {
this.onSubmit()
}
}
onSubmit=()=>{
this.props.verifyOtp(payload)
}
onResendOtp=()=>{
this.props.sendOtp(payload,locationData)
this.setState({ isResendDisabled: true, opacity: 0.5 });
setTimeout(() => {
this.setState({ isResendDisabled: false, opacity: 1 });
}, 30000);
}
render() {
return (<View><Elements></View>)
}
}
function mapStateToProps(state) {
return {
common: state.common
}
}
function mapDispatchToProps(dispatch) {
return {
...bindActionCreators({ verifyOtp,sendOtp }, dispatch)
}
}
export default connect(mapStateToProps,mapDispatchToProps)(VerifyOtp);
VerifyOtp screen used to verify the otp.
The problem is,If i move back to enterUserinfo screen and move again to the verifyOtp screen im getting the warning message
'Warning: Can\'t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application
What is the reason for the warning , and how tackle the issue?
This happens when you call an async function followed by setstate.
A simple work around would be like this:
constructor(props) {
this.state = {
...
this.isCancelled: false
}
}
componentWillMount() {
setTimeout(() => {
!this.state.isCancelled && this.setState({ isResendDisabled: false,
opacity: 1 });
}, 30000);
}
and in componentWillUnmount
componentWillUnmount() {
// setting it true to avoid setState waring since componentWillMount is async
this.state.isCancelled = true;
}

How Redirect to Login if page is protected and the user is not signed in?

In my App I have some public screens that are accessible even if the user is not logged in, and some screens are protected (you must be logged in to access them).
My solution to the problem is to check the component willFocus Listener and if not logged in, the user should be redirected to the loginPage.
export async function ProtectRoute(navigation){
//if page will enter and the user is not authenticated return to login
navigation.addListener(
'willFocus',
async () => {
let token = await getTokenAsync();
if(!token){
navigation.navigate('Login');
}
})
}
In my screen I Call this function in ComponentWillMount lifecycle.
The issue is that it takes like a second to verify the token and the page is displayed briefly.
How can I make it so that he goes directly to the Login Page without that lag ?
I wrote a quick example below. You can examine and use it.
import React, { Component } from "react";
import { Text, View } from "react-native";
const withAuth = WrappedComponent => {
class AuthenticationScreen extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false
};
props.navigation.addListener("willFocus", async () => {
await this.checkAuth();
});
}
remoteReuqest = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, 2000);
});
};
checkAuth = async () => {
const result = await this.remoteReuqest();
if (result) {
this.setState({
isAuthenticated: true
});
} else {
this.props.navigation.navigate("Login");
}
};
render() {
if (!this.state.isAuthenticated) {
return <Text>Waiting...</Text>;
}
return <WrappedComponent {...this.props} />;
}
}
return AuthenticationScreen;
};
export default withAuth;
You can use it as follows.
import React, { Component } from "react";
import { Text, StyleSheet, View } from "react-native";
import withAuth from "./withAuth";
class ContactScreen extends Component {
render() {
return (
<View>
<Text> Contact Screen </Text>
</View>
);
}
}
const styles = StyleSheet.create({});
const extendedComponent = withAuth(ContactScreen);
extendedComponent.navigationOptions = {
title: "Contact"
};
export default extendedComponent;
The issue is that it takes like a second to verify the token and the page is displayed briefly.
The reason is because reading/writing from/to AsyncStorage is an asychronous operation.
In my screen I Call this function in ComponentWillMount lifecycle.
I suggest you to not use ComponentWillMount lifecycle because it's deprecated and it will be removed from React (https://reactjs.org/docs/react-component.html#unsafe_componentwillmount)
After this introduction, now i show you how I have achieved this in my app: CONTEXT API! (https://reactjs.org/docs/context.html)
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
How to implement context api:
the context will be the 'state' of your App.js file. You root App.js will be the provider of the context, while other views which will need the context are called the consumers of the context.
First of all, you need to create a 'skeleton' of your context into a separate file, something like this:
// AuthContext.js
import React from 'react'
const AuthContext = React.createContext({
isLogged: false,
login: () => {},
logout: () => {}
})
export default AuthContext
Your App.js will import, contain and initialize the context:
// App.js
// all necessary imports
import AuthContext from '....'
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
isAuth: false,
login: this.login,
logout: this.logout
}
login = async userData => {
// do your login stuff and then
this.setState({ isAuth: true })
}
logout = async () => {
// do your logout stuff and then
this.setState({ isAuth: false })
}
async ComponentDidMount () {
// check the asyncStorage here and then, if logged:
this.setState({ isAuth: true })
}
render () {
return (
<AuthContext.Provider value={this.state}>
<AppContainer />
</AuthContext.Provider>
)
}
Then, into the View contained into AppContainer, you could access context like this:
import AuthContext from '.....'
// all necessary imports
export default class YourView extends React.Component<Props, State> {
constructor (props) {
super(props)
this.props = props
this.state = { ... }
}
// THIS IS IMPORTANT
static contextType = AuthContext
// with this, you can access the context through 'this.context'
ComponentDidMount () {
if (!this.context.isAuth) this.props.navigation.navigate('login')
}
Advantages of this approach:
Checking a boolean is so fast that you will not notice a blank screen.
Sharing an Authentication Context everywhere in you app
Making the access to asyncstorage only the first time that app mounts and not everytime you need to check if the user is logged
Sharing methods to login/logout everywhere in your app

React-Native: Conditional App launch in wix/react-native-navigation - v1

I am using wix/react-native-navigation - v1 in my react native project and I want to launch my App based on a condition as follows:
Launch App
Read credentials from storage (AsyncStorage)
If credentials found, then
Start App with Home screen
Else
Start App with Login Screen
How can I achieve this?
I have index.js
import App from './App';
App.js
...
Navigation.registerComponent("myApp.AuthScreen", () => AuthScreen);
Navigation.registerComponent("myApp.HomeScreen", () => HomeScreen);
...
// Start a App
Navigation.startSingleScreenApp({
screen: {
screen: "myApp.AuthScreen",
title: "Login"
}
});
You can have two functions that initialize single-screen apps and then call the one that fulfills the requirements.
...
Navigation.registerComponent("myApp.AuthScreen", () => AuthScreen);
Navigation.registerComponent("myApp.HomeScreen", () => HomeScreen);
...
function startHomeScreen() {
Navigation.startSingleScreenApp({
screen: {
screen: "myApp.HomeScreen",
title: "Login"
}
});
}
function startAuthScreen() {
Navigation.startSingleScreenApp({
screen: {
screen: "myApp.AuthScreen",
title: "Home"
}
});
}
function init() {
if(...) {
startAuthScreen();
} else {
startHomeScreen();
}
}
It worked! I am not sure why the app kept hanging on splashscreen. Following is the exact code:
const __init__ = () => {
try {
AsyncStorage.getItem("MY-KEY")
.then((value) => {
if (value) {
startHomeScreen();
} else {
startAuthScreen();
}
});
} catch (e) {
startAuthScreen();
}
};
__init__();
Thanks #Filip Ilievski !
Navigation.registerComponent("RootScreen", () => RootScreen);
Navigation.startSingleScreenApp({
screen: {
screen: "RootScreen",
title: "Root"
}
});
For this scenarios you can create one additional component like below.
This additional component will check your condition in async storage and decide which view to load first
import AuthScreen from './AuthScreen';
import HomeScreen from './HomeScreen';
class RootScreen {
constructor(props) {
super(props);
this.state = {
loaded: false,
screenToLoad: ''
};
}
componentDidMount() {
this.checkRoute();
}
checkRoute = () => {
AsyncStorage.getItem("MY-KEY")
.then((value) => {
this.setState({
loaded: true,
screenToLoad: value
});
});
}
renderRoute = () => {
const { screenToLoad } = this.state;
switch(screenToLoad) {
case 'AuthScreen':
return <AuthScreen />;
case 'HomeScreen':
return <HomeScreen />
default:
return null;
}
}
render () {
const { loaded } = this.state;
if (!loaded) return null;
return this.renderRoute();
}
}

React-Native : change state when app is in background

I create a simple app that using React Native AppState:
import React, {Component} from 'react'
import {AppState, Text , View} from 'react-native'
export default class AppStateExample extends React.Component {
constructor(props){
super(props);
this.state = {
name:'not change'
}
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if(AppState.currentState=='background'){
console.log('background mode');
this.setState({name:'back'});
}
if(AppState.currentState =='active'){
//...
}
};
render() {
return (
<View>
<Text>State Name : {this.state.name}</Text>
</View>
);
}
}
And when I try switch app from foreground to background and then background to foreground console.log('background mode'); work very well and console
print 'background mode'
BUT
The this.setState({name:'back'}); not working and I see 'not change' text in view
Actually, based on React Native Docs on AppState change for Functional Component I prefer to write code like below:
import { useRef, useState, useEffect } from "react";
import { AppState } from "react-native";
const AppStateManager = () => {
const appState = useRef(AppState.currentState);
const [appStateVisible, setAppStateVisible] = useState(appState.current);
useEffect(() => {
AppState.addEventListener("change", handleAppStateChange);
return () => {
AppState.removeEventListener("change", handleAppStateChange);
};
}, []);
const handleAppStateChange = (nextAppState) => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
console.log("App has come to the foreground!");
}
appState.current = nextAppState;
setAppStateVisible(appState.current);
console.log("AppState", appState.current);
};
return null;
};
export default AppStateManager;
Surely, we can use this component in the root of the project just like a React Component:
~~~
<App>
~~
<AppStateManager />
~~
.
.
.
It is because, AppState.addEventListener('change', this._handleAppStateChange); is too late to register.
You probably want to listen AppState the first thing in your app before main components get loaded and pass down the values probably by your state management library
I would go for a switch that wrap all endPoint
note: to get appState status AppState.currentState
this.state = {
appState: AppState.currentState
// https://facebook.github.io/react-native/docs/appstate.html
};
componentWillMount() {
AppState.addEventListener('change', () => this._handleAppStateChange());
};
componentWillUnmount() {
AppState.removeEventListener('change', () => this._handleAppStateChange());
}
_handleAppStateChange() {
// https://facebook.github.io/react-native/docs/appstate.html
const {
appState
} = this.state;
console.warn({
appState
})
this.fetchData().catch(error => error);
switch (appState) {
case 'active': //The app is running in the foreground
this.onStart();
break;
case 'background': // The app is running in the background. The user is either
this.onEnd();
console.warn('background');
break;
case 'inactive':
// The app transitioning between foreground & background or entering the Multitasking view or in the event of an incoming call
console.warn('Inactive');
break;
default:
console.warn('_handleAppStateChange default');
}
}

React Native AsyncStorage fetches data after rendering

I am using AsyncStorage in ComponentWillMount to get locally stored accessToken, but it is returning the promise after render() function has run. How can I make render() wait until promise is completed? Thank you.
You can't make a component wait to render, as far as I know. What I've done in the app I'm working on is to add a loading screen until that promise from AsyncStorage resolves. See the examples below:
//
// With class component syntax
//
import React from 'react';
import {
AsyncStorage,
View,
Text
} from 'react-native';
class Screen extends React.Component {
state = {
isLoading: true
};
componentDidMount() {
AsyncStorage.getItem('accessToken').then((token) => {
this.setState({
isLoading: false
});
});
},
render() {
if (this.state.isLoading) {
return <View><Text>Loading...</Text></View>;
}
// this is the content you want to show after the promise has resolved
return <View/>;
}
}
//
// With function component syntax and hooks (preferred)
//
import React, { useEffect } from 'react';
import {
AsyncStorage,
View,
Text
} from 'react-native';
const Screen () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
AsyncStorage.getItem('accessToken').then((token) => {
setIsLoading(false);
});
}, [])
if (isLoading) {
return <View><Text>Loading...</Text></View>;
}
// this is the content you want to show after the promise has resolved
return <View/>;
}
Setting the isLoading property in state will cause a re-render and then you can show the content that relies on the accessToken.
On a side note, I've written a little library called react-native-simple-store that simplifies managing data in AsyncStorage. Hope you find it useful.
Based on react-native doc, you can do something like this:
import React, { Component } from 'react';
import {
View,
} from 'react-native';
let STORAGE_KEY = '#AsyncStorageExample:key';
export default class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
loaded: 'false',
};
}
_setValue = async () => {
try {
await AsyncStorage.setItem(STORAGE_KEY, 'true');
} catch (error) { // log the error
}
};
_loadInitialState = async () => {
try {
let value = await AsyncStorage.getItem(STORAGE_KEY);
if (value === 'true'){
this.setState({loaded: 'true'});
} else {
this.setState({loaded: 'false'});
this._setValue();
}
} catch (error) {
this.setState({loaded: 'false'});
this._setValue();
}
};
componentWillMount() {
this._loadInitialState().done();
}
render() {
if (this.state.loaded === 'false') {
return (
<View><Text>Loading...</Text></View>
);
}
return (
<View><Text>Main Page</Text></View>
);
}
}
you can use react-native-easy-app that is easier to use than async storage.
this library is great that uses async storage to save data asynchronously and uses memory to load and save data instantly synchronously, so we save data async to memory and use in app sync, so this is great.
import { XStorage } from 'react-native-easy-app';
import { AsyncStorage } from 'react-native';
const initCallback = () => {
// From now on, you can write or read the variables in RNStorage synchronously
// equal to [console.log(await AsyncStorage.getItem('isShow'))]
console.log(RNStorage.isShow);
// equal to [ await AsyncStorage.setItem('token',TOKEN1343DN23IDD3PJ2DBF3==') ]
RNStorage.token = 'TOKEN1343DN23IDD3PJ2DBF3==';
// equal to [ await AsyncStorage.setItem('userInfo',JSON.stringify({ name:'rufeng', age:30})) ]
RNStorage.userInfo = {name: 'rufeng', age: 30};
};
XStorage.initStorage(RNStorage, AsyncStorage, initCallback);
React-native is based on Javascript which does not support blocking functions.Also this makes sense as we don't want the UI to get stuck or seem unresponsive.
What you can do is handles this in the render function. i.e Have a loading screen re-render it as you as you get the info from the AsyncStorage