Change state dynamically based on the external Internet connectivity - React (offline/online) - javascript

I have a React component, that includes the availability flag of Internet connectivity. UI elements have to be dynamically changed according to state real-time. Also, functions behave differently with the changes of the flag.
My current implementation polls remote API using Axios in every second using interval and updates state accordingly. I am looking for a more granular and efficient way to do this task to remove the 1-second error of state with the minimum computational cost. Considered online if and only if device has an external Internet connection
Current implementation :
class Container extends Component {
constructor(props) {
super(props)
this.state = {
isOnline: false
};
this.webAPI = new WebAPI(); //Axios wrapper
}
componentDidMount() {
setInterval(() => {
this.webAPI.poll(success => this.setState({ isOnline: success });
}, 1000);
}
render() {
return <ChildComponent isOnline={this.state.isOnline} />;
}
}
Edited:
Looking for a solution capable of detecting external Internet connectivity. The device can connect to a LAN which doesn't have an external connection. So, it is considered offline. Considers online if and only if device has access to external Internet resources.

You can use https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event
window.addEventListener('offline', (event) => {
console.log("The network connection has been lost.");
});
and https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event
for checking when you're back online
window.addEventListener('online', (event) => {
console.log("You are now connected to the network.");
});

Method one: Using legacy browser API - Navigator.onLine
Returns the online status of the browser. The property returns a boolean value, with true meaning online and false meaning offline. The property sends updates whenever the browser's ability to connect to the network changes. The update occurs when the user follows links or when a script requests a remote page. For example, the property should return false when users click links soon after they lose internet connection.
You can add it to your component lifecycle:
Play with the code below using Chrome dev tools - switch "Online" to "Offline" under the Network tab.
class App extends React.PureComponent {
state = { online: window.navigator.onLine }
componentDidMount() {
window.addEventListener('offline', this.handleNetworkChange);
window.addEventListener('online', this.handleNetworkChange);
}
componentWillUnmount() {
window.removeEventListener('offline', this.handleNetworkChange);
window.removeEventListener('online', this.handleNetworkChange);
}
handleNetworkChange = () => {
this.setState({ online: window.navigator.onLine });
}
render() {
return (
<div>
{ this.state.online ? 'you\'re online' : 'you\'re offline' }
</div>
);
}
}
ReactDOM.render(
<App />
, document.querySelector('#app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
However, I think this isn't what you want, you wanted a real-time connection validator.
Method two: Checking internet connection by using it
The only solid confirmation you can get if the external internet connectivity is working is by using it. The question is which server you should call to minimize the cost?
There are many solutions on the internet for this, any endpoint that responds with a quick 204 status is perfect, e.g.:
calling to Google server (for it being the most battle-tested (?) )
calling its cached JQuery script endpoint (so even if the server is down, you should still be able to get the script as long as you have a connection)
try fetching an image from a stable server (e.g.: https://ssl.gstatic.com/gb/images/v1_76783e20.png + date timestamp to prevent caching)
IMO, if you are running this React app on a server, it makes the most sense to call to your own server, you can call a request to load your /favicon.ico to check the connection.
This idea (of calling your own server) has been implemented by many libraries, such as Offline, is-reachable, and is widely used across the community. You can use them if you don't want to write everything by yourself. (Personally I like the NPM package is-reachable for being simple.)
Example:
import React from 'react';
import isReachable from 'is-reachable';
const URL = 'google.com:443';
const EVERY_SECOND = 1000;
export default class App extends React.PureComponent {
_isMounted = true;
state = { online: false }
componentDidMount() {
setInterval(async () => {
const online = await isReachable(URL);
if (this._isMounted) {
this.setState({ online });
}
}, EVERY_SECOND);
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
return (
<div>
{ this.state.online ? 'you\'re online' : 'you\'re offline' }
</div>
);
}
}
I believe what you have currently is already fine, just make sure that it is calling the right endpoint.
Similar SO questions:
Detect the Internet connection is offline?
Detect network connection in React Redux app - if offline, hide component from user
https://stackoverflow.com/Questions/3181080/How-To-Detect-Online-Offline-Event-Cross-Browser

Setup a custom hook
Setup a hook with the online, offline events. then update a state and return it. This way you can use it anywhere in your app with an import. Make sure you clean up with the return function. If you don't you will add more and more event listeners each time a component using the hook mounts.
const onlineHook = () => {
const {isOnline, setOnline} = React.useState();
React.useEffect(() => {
const goOnline = function(event){
setOnline(true);
});
const goOffline = function(event){
setOnline(false);
});
window.addEventListener('offline', goOffline);
window.addEventListener('online', goOnline);
return () => {
window.removeEventListener('offline', goOffline);
window.removeEventListener('online', goOnline);
}
}, [])
return isOnline
}
To use this just import the above hook and call it like this.
const isOnline = onlineHook(); // true if online, false if not

You can create a component to share between all subcomponents
used:
import React, { useState, useEffect } from "react";
export default function NetworkChecker() {
const [networkStatus, setNetworkStatus] = useState(true)
useEffect(() => {
window.addEventListener('offline', (event) => {
setNetworkStatus(false)
});
window.addEventListener('online', (event) => {
setNetworkStatus(true)
});
return function cleanupListener() {
window.removeEventListener('online', setNetworkStatus(true))
window.removeEventListener('offline', setNetworkStatus(false))
}
},[])
if (networkStatus) {
return <div className={"alert-success"}>Online</div>
} else {
return <div className={"alert-danger"}>Offline</div>
}
}

Related

How to write a event listener to update global variable in react native navigation

My navigation system is bottomTabNavigator
In Dashboard screen I have bluetooth connection listner that update global variable like so
BluetoothManager.connect(row.address).then(
success => {
Alert.alert('Alert', 'Success')
global.appState = `Successfully connected to ${row.name}`
}
When the connection is lost I run this
DeviceEventEmitter.addListener(
BluetoothManager.EVENT_CONNECTION_LOST,
() => {
console.log('EVENT_CONNECTION_LOST')
global.appState = 'No device connected'
},
)
In that screen everything works fine but on the other screen no way to update that variable by simply doing setState so I have used an infinite loop but I know it's bad for performance, how can I only update that component when variable changes?
// Tickets.js
UNSAFE_componentWillUpdate() {
this.appState = setTimeout(() => {
this.setState(() => ({appState: global.appState}))
console.log('global.appState :', global.appState);
}, 1000);
}
UNSAFE_componentWillMount() {
clearTimeout(this.appState);
}

How to detect "net::ERR_CONNECTION_REFUSED"

i'm use Vue 2 and i wanna detect sockjs errors and show notification.
(Like 'Connection lost','connection timeout' etc. )
I have no idea how to do it
The browser has a built in method called navigator.onLine, which returns either true or false. Now to watch for connection changes you can add an event listener on window,
window.addEventListener('offline', (e) => { console.log('offline'); });
window.addEventListener('online', (e) => { console.log('online'); });
You can incorporate this into a Vue component with something like:
export default {
data() {
return {
online: navigator.onLine
};
},
mounted() {
window.addEventListener("online", this.onchange);
window.addEventListener("offline", this.onchange);
this.onchange();
},
beforeDestroy() {
window.removeEventListener("online", this.onchange);
window.removeEventListener("offline", this.onchange);
},
methods: {
onchange() {
this.online = navigator.onLine;
this.$emit(this.online ? "online" : "offline");
}
}
};
And then use v-if="!online" to selectively render you're offline banner.
Alternatively, take a look at: v-offline, it instead works by pinging an endpoint, which has the advantage of being able to detect when the user is online but with very poor internet connection (loading), however is an overall less efficient approach.
import offline from 'v-offline';
export default {
components: {
offline
},
methods: {
handleConnectivityChange(status) {
console.log(status);
}
}
}
For most sock.js methods, you can get this information from the Event parameter returned by the callback. But for detecting network connection, and other common tasks, it's usually more robust to do natively, as outlined above.

Detect if the user is connected to the internet?

I'd like to route the user to a certain screen, in case he is not connected to the internet.
I just can't detect if he is connected or not.
I tried this code, but did not work:
async componentWillMount()
{
if (!await NetInfo.isConnected)
{
this.props.navigation.navigate('Saved');
}
}
Any tested solution to suggest?
Try await NetInfo.isConnected.fetch()
ref : https://facebook.github.io/react-native/docs/netinfo.html#isconnected
You can check using NetInfo .
for that you have to add connectionChange event listener like this
componentDidMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange.bind(this));
NetInfo.isConnected.fetch().done(
(isConnected) => { this.setState({ isConnected: isConnected }); }
);
and then remove the event listener in componentWillUnmount
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
And finally the handler method for connection change. I am storing the status in device local storage you can do whatever you want.
handleConnectionChange = (isConnected) => {
if (isConnected) {
//ToastAndroid.show('Data sync in process...', ToastAndroid.SHORT);
AsyncStorage.getItem('offlineData')
.then((json) => JSON.parse(json))
.then((data) => {
console.log(JSON.stringify(data));
});
}
else { ToastAndroid.show('You are offline.', ToastAndroid.SHORT); }
this.setState({ isConnected: isConnected });
}
Don't forget to add NetInfo from react-native :)
Another solution to your case (one without using isConnected property) is to use the object returned from the event handler directly like that:
componentDidMount() {
NetInfo.addEventListener('connectionChange', this.handleNetworkChange);
}
componentWillUnmount() {
NetInfo.removeEventListener('connectionChange', this.handleNetworkChange);
}
handleNetworkChange = (info) => {
if (info.type === 'none') {
this.props.navigation.navigate('Saved');
}
};
According to NetInfo documentation:
connectionChange event fires when the network status changes. The argument to the event handler is an object with keys:
type: A ConnectionType (listed above)
effectiveType: An EffectiveConnectionType (listed above)
The connection type can be one of the following : none, wifi, cellular, unknown.
Ideally you can store this information to your redux store and the listener to a root component.
We had a weird bug when using isConnected similar to the one you mentioned #Gabriel Bleu but for us, the NetInfo.isConnected.fetch() returned false only when the Android device was awake after some period of inactivity.We used it to display offline warning for users, so the warning never left. I found this solution on a Spencer Carli's course and it seems to work better but depending on your needs, you might want to use isConnected combined with the above code.
This is a great example to check online or offline and even you can have connection change information too. Source
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
NetInfo.isConnected.removeEventListener(
'connectionChange',
handleFirstConnectivityChange
);
}
NetInfo.isConnected.addEventListener(
'connectionChange',
handleFirstConnectivityChange
);
There are two issues with your code currently.
In newer versions of react life-cycle method componentWillMount is deprecated.
Newer versions of react-native have extracted the NetInfo Module out of the core. Use #react-native-community/netinfo instead.
In order to achieve the desired behavior you should do something like this.
import NetInfo from "#react-native-community/netinfo";
class CheckConnection extends Component {
componentDidMount() {
NetInfo.fetch().then(state => {
handleConnectionState(state)
});
}
handleConnectionState(state) {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
... your code to handle the lack of connection
}
}

Meteor minimongo shows user logged in on client before server

It appears that if a user is logged in to a Meteor application, then loses and regains their DDP connection, there is a short moment where the client believes that it is logged in before the server does.
For example, I have a container component that updates according to the result of Meteor.loggingIn():
const MainNavigationContainer = createContainer(props => {
return {
meteorReady: Meteor.loggingIn() === false
}
}, MainNavigation);
In the MainNavigation component, I run a Meteor method which should return a result based on the user's _id (I have tried to remove irrelevant code):
class MainNavigation extends Component {
componentWillReceiveProps(nextProps) {
this.setInitialRoute(nextProps);
}
setInitialRoute = (props) => {
// Set up initial route
if (props.meteorReady) {
if (!Meteor.user()) {
this.setState({initialRoute: routes[1]});
} else {
Meteor.call('/user/events/isActive', (e, eventId) => {
if (eventId) {
// Do some stuff
} else {
// Do some other stuff
}
});
}
}
};
render() {
return (
this.props.meteorReady && this.state.initialRoute ?
<Navigator
ref={navigator => this.navigator = navigator}
initialRoute={this.state.initialRoute}
renderScene={(route, navigator) => { ... }}
/> : (
<View style={styles.container}>
<ActivityIndicator animating={true}/>
</View>
)
)
}
}
The /user/events/isActive method call should only be run if Meteor.user() is defined (which should mean the user is logged in). However, when I look at the server call:
Meteor.methods({
'/user/events/isActive': function () {
console.log('userId:', this.userId);
if (this.userId) {
const member = Members.findOne({userId: this.userId});
if (member) {
return member.eventId;
}
return false;
}
return false;
}
});
The first call of this method (after a DDP disconnect and reconnect) ends up with this.userId being equal to null.
Basically, if Meteor.user() is defined on the client, I expect this.userId to be defined on the server. However, it appears that the minimongo on the client is giving a false positive before they are actually logged in (when they disconnect and reconnect).
My question is: If Meteor.user() is defined on the client, can I safely assume that this.userId will be defined on the server? As of now, I would say I cannot, so is there any other way for me to reliably determine if the user is truly logged in from the client side?
After a lot of debugging, I have finally figured out what was going on.
The container sends new props to its child component any time a reactive computation is invalidated. Also, Meteor methods are asynchronous, and if they are not resolved on the server, the client will continue to try to get a response until a reconnect. However, the props that are passed into setInitialRoute are the nextProps from componentWillReceiveProps.
So, what was happening was that a call was being made right as the meteor server was disconnected, and the resolution of that call occurred after the reconnect. So the previous call of the meteor method was being evaluated, giving me a null for the this.userId.
To solve this, I just had to put a conditional within the callback of the meteor method to ensure that it was evaluated when the user is actually logged in (using the current props rather than the passed in nextProps):
Meteor.call('/user/events/isActive', (e, eventId) => {
if (this.props.meteorReady) {...}
});
This prevents the result from that call of the method from being evaluated on the client, and solved my problem.

react-http-request does not change loading state after second request

I am now using react-http-request in my React.js component to send request and process the response. The URL parameter passed is relevant to the component state such that when the state changes, the component will be re-rendered and change the component display.
This works on the first request. However, I found that the component does not return a {load: true} after the second request, and I wonder how to solve this.
I tried to call the onRequest method and set the loading state for the component, but I cannot change the loading state after the request is finished (as render function cannot change the state).
react-http-request: https://github.com/mbasso/react-http-request
My Code is like below:
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType
}
},
// ... details emitted.
render: function(){
return (<Request
url={config.url.api + "/" + this.state.queryType}
method="get"
accept="application/json"
query={{ several parameter }}
>
{
({error, result, loading}) => {
if (loading || error) {
return <Loading />
}
else {
// process the result here.
}
}
}
</Request>)
}
So, my initial recommendation would be that you use some state management library (redux, mobx, etc) but it is not necessary to get a working example of your code, so:
import fetch from 'whatwg-fetch'; // gives compatibility with older browsers
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType,
content: null
}
},
componentWillMount: function() {
this.fetchContent();
},
fetchContent: function() {
const uri = config.url.api + "/" + this.state.queryType;
// You can use w/e you want here (request.js, etc), but fetch is the latest standard in the js world
fetch(uri, {
method: 'GET',
// More properties as you see fit
})
.then(response => response.json()) // might need to do this ;)
.then(response => {
this.setState({
content: response
})
})
},
// ...
render: function(){
const content = this.state.content? (
// render your content based on this.state.content
): (
<Loading />
)
return content;
}
});
Haven't tested this code, but there are some nice benefits to it:
The http request is not dependant on React, which should (in theory) be for UI components.
The fetching mechanism is decoupled, and can be re-used at any point in the component lifecycle
In my opinion easier to read, divided into smaller logical chunks
I would recommend reading the React Component Lifecycle.
In this case, I read the source code of the react-http-request, and found that there is a weakness that after accepting and sending the second request, the component failed to update the state of "loading" returns.
// starts from Line 49
value: function componentWillReceiveProps(nextProps) {
if (JSON.stringify(this.props) === JSON.stringify(nextProps)) {
return;
}
this.request.abort();
this.performRequest(nextProps);
}
Manually changed the state of loading here can help reset the loading after each request received.
I changed the source code of this lib, and sent the pull request to the repo. It's now merged into master and ejected a new release.
See: https://github.com/mbasso/react-http-request/pull/3
Thus, this problem can be solved by keeping the lib update to the release (currently it is 1.0.3).

Categories

Resources