From 7374c2bdc9260fdb490917381762787fd591c2b6 Mon Sep 17 00:00:00 2001 From: Adrien Thiery Date: Wed, 1 Nov 2017 19:23:26 -0400 Subject: [PATCH] Added ability to check connectivity regularly (#32) --- README.md | 21 ++++++++------- src/checkConnectivityInterval.js | 18 +++++++++++++ src/checkInternetAccess.js | 5 ---- src/withNetworkConnectivity.js | 44 +++++++++++++++++++++++--------- 4 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 src/checkConnectivityInterval.js diff --git a/README.md b/README.md index 9680eed5..d6609eb5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Handful of utilities you should keep in your toolbelt to handle offline/online connectivity in React Native. It supports both iOS and Android platforms. You can leverage all the functionalities provided or just the ones that suits your needs, the modules are conveniently decoupled. -Check out [this medium article](https://blog.callstack.io/your-react-native-offline-tool-belt-795abd5f0183) to see the power of the library with real world examples! 🚀 +Check out [this medium article](https://blog.callstack.io/your-react-native-offline-tool-belt-795abd5f0183) to see the power of the library with real world examples! 🚀 ## Contents @@ -28,7 +28,7 @@ Check out [this medium article](https://blog.callstack.io/your-react-native-offl ## Motivation When you are building your React Native app, you have to expect that some users may use your application in offline mode, for instance when travelling on a Plane (airplane mode) or the underground (no signal). How does your app behaves in that situation? Does it show an infinite loader? Can the user still use it seamlessly? -Having an offline first class citizen app is very important for a successful user experience. React Native ships with `NetInfo` module in order to detect internet connectivity. The API is pretty basic and it may be sufficient for small apps but its usage gets cumbersome as your app grows. Besides that, it only detects network connectivity and does not garantee internet access so it can provide false positives. +Having an offline first class citizen app is very important for a successful user experience. React Native ships with `NetInfo` module in order to detect internet connectivity. The API is pretty basic and it may be sufficient for small apps but its usage gets cumbersome as your app grows. Besides that, it only detects network connectivity and does not guarantee internet access so it can provide false positives. This library aims to gather a variety of modules that follow React and redux best practises, in order to make your life easier when it comes to deal with internet connectivity in your React Native application. @@ -40,6 +40,7 @@ This library aims to gather a variety of modules that follow React and redux bes - **A step further than `NetInfo` detecting internet access besides network connectivity** - Offline queue support to automatically re-dispatch actions when connection is back online or **dismiss actions based on other actions dispatched (i.e navigation related)** - Typed with Flow +- Check connectivity regularly (optional) ## Installation @@ -74,6 +75,7 @@ type Config = { timeout?: number = 3000, pingServerUrl?: string = 'https://google.com', withExtraHeadRequest?: boolean = true, + checkConnectionInterval?: number = 0, } ``` @@ -86,6 +88,8 @@ type Config = { `withExtraHeadRequest`: flag that denotes whether the extra ping check will be performed or not. Defaults to `true`. +`checkConnectionInterval`: the interval (in ms) you want to ping the server at. The default is 0, and that means it is not going to regularly check connectivity. + ##### Usage ```js import React from 'react'; @@ -198,7 +202,7 @@ let App = () => ( App = withNetworkConnectivity({ withRedux: true // It won't inject isConnected as a prop in this case -})(App); +})(App); const Root = () => ( @@ -225,7 +229,7 @@ createNetworkMiddleware(config: Config): ReduxMiddleware type Config = { regexActionType?: RegExp = /FETCH.*REQUEST/, - actionTypes?: Array = [] + actionTypes?: Array = [] } ``` @@ -257,7 +261,7 @@ export const fetchUser = (url) => { console.error(error); }); }; - + thunk.interceptInOffline = true; // This is the important part return thunk; // Return it afterwards }; @@ -453,7 +457,7 @@ export default function configureStore(callback) { }); }, ); - + return store; } ``` @@ -475,10 +479,10 @@ class App extends Component { store: configureStore(() => this.setState({ isLoading: false })), }; } - + render() { if (this.state.isLoading) return null; - + return ( @@ -522,4 +526,3 @@ Thanks to Spencer Carli for his awesome article about [Handling Offline actions ### License MIT - diff --git a/src/checkConnectivityInterval.js b/src/checkConnectivityInterval.js new file mode 100644 index 00000000..a0e20e14 --- /dev/null +++ b/src/checkConnectivityInterval.js @@ -0,0 +1,18 @@ +// @flow + +let interval = null; + +export const setupConnectivityCheckInterval = ( + connectivityCheck: Function, + checkConnectionInterval: number, +) => { + if (checkConnectionInterval && !interval) { + interval = setInterval(connectivityCheck, checkConnectionInterval); + } +}; + +export const clearConnectivityCheckInterval = () => { + if (interval) { + clearInterval(interval); + } +}; diff --git a/src/checkInternetAccess.js b/src/checkInternetAccess.js index 7590df16..041e0a0b 100644 --- a/src/checkInternetAccess.js +++ b/src/checkInternetAccess.js @@ -1,14 +1,9 @@ /* @flow */ export default function checkInternetAccess( - isConnected: boolean, timeout: number = 3000, address: string = 'https://google.com', ): Promise { - if (!isConnected) { - return Promise.resolve(false); - } - return new Promise((resolve: (value: boolean) => void) => { const tm = setTimeout(() => { resolve(false); diff --git a/src/withNetworkConnectivity.js b/src/withNetworkConnectivity.js index 0802dcee..ee4c2dea 100644 --- a/src/withNetworkConnectivity.js +++ b/src/withNetworkConnectivity.js @@ -7,6 +7,10 @@ import hoistStatics from 'hoist-non-react-statics'; import { connectionChange } from './actionCreators'; import reactConnectionStore from './reactConnectionStore'; import checkInternetAccess from './checkInternetAccess'; +import { + setupConnectivityCheckInterval, + clearConnectivityCheckInterval, +} from './checkConnectivityInterval'; type Arguments = { withRedux?: boolean, @@ -25,6 +29,7 @@ const withNetworkConnectivity = ( timeout = 3000, pingServerUrl = 'https://google.com', withExtraHeadRequest = true, + checkConnectionInterval = 0, }: Arguments = {}, ) => (WrappedComponent: ReactClass<*>) => { if (typeof withRedux !== 'boolean') { @@ -55,32 +60,43 @@ const withNetworkConnectivity = ( 'connectionChange', withExtraHeadRequest ? this.checkInternet - : this.handleConnectivityChange, + : this.handleNetInfoChange, ); + // On Android the listener does not fire on startup if (Platform.OS === 'android') { - NetInfo.isConnected.fetch().then((isConnected: boolean) => { - if (withExtraHeadRequest) { - this.checkInternet(isConnected); - } else { - this.handleConnectivityChange(isConnected); - } - }); + NetInfo.isConnected.fetch().then(withExtraHeadRequest + ? this.handleNetInfoChange + : this.handleConnectivityChange + ); } + + setupConnectivityCheckInterval( + this.checkInternet, + checkConnectionInterval, + ); } componentWillUnmount() { NetInfo.isConnected.removeEventListener( 'connectionChange', withExtraHeadRequest - ? this.checkInternet + ? this.handleNetInfoChange : this.handleConnectivityChange, ); + clearConnectivityCheckInterval(); } - checkInternet = (isConnected: boolean) => { + handleNetInfoChange = (isConnected: boolean) => { + if (!isConnected) { + this.handleConnectivityChange(isConnected); + } else { + this.checkInternet(); + } + }; + + checkInternet = () => { checkInternetAccess( - isConnected, timeout, pingServerUrl, ).then((hasInternetAccess: boolean) => { @@ -91,6 +107,7 @@ const withNetworkConnectivity = ( handleConnectivityChange = (isConnected: boolean) => { const { store } = this.context; reactConnectionStore.setConnection(isConnected); + // Top most component, syncing with store if ( typeof store === 'object' && @@ -98,7 +115,10 @@ const withNetworkConnectivity = ( withRedux === true ) { const actionQueue = store.getState().network.actionQueue; - store.dispatch(connectionChange(isConnected)); + + if (isConnected !== store.getState().network.isConnected) { + store.dispatch(connectionChange(isConnected)); + } // dispatching queued actions in order of arrival (if we have any) if (isConnected && actionQueue.length > 0) { actionQueue.forEach((action: *) => {