Skip to content

Commit

Permalink
Added ability to check connectivity regularly (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienthiery authored and Raúl Gómez Acuña committed Nov 1, 2017
1 parent bd7222f commit 7374c2b
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 26 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.

Expand All @@ -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

Expand Down Expand Up @@ -74,6 +75,7 @@ type Config = {
timeout?: number = 3000,
pingServerUrl?: string = 'https://google.com',
withExtraHeadRequest?: boolean = true,
checkConnectionInterval?: number = 0,
}
```

Expand All @@ -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';
Expand Down Expand Up @@ -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 = () => (
<Provider store={store}>
Expand All @@ -225,7 +229,7 @@ createNetworkMiddleware(config: Config): ReduxMiddleware

type Config = {
regexActionType?: RegExp = /FETCH.*REQUEST/,
actionTypes?: Array<string> = []
actionTypes?: Array<string> = []
}
```

Expand Down Expand Up @@ -257,7 +261,7 @@ export const fetchUser = (url) => {
console.error(error);
});
};

thunk.interceptInOffline = true; // This is the important part
return thunk; // Return it afterwards
};
Expand Down Expand Up @@ -453,7 +457,7 @@ export default function configureStore(callback) {
});
},
);

return store;
}
```
Expand All @@ -475,10 +479,10 @@ class App extends Component {
store: configureStore(() => this.setState({ isLoading: false })),
};
}

render() {
if (this.state.isLoading) return null;

return (
<Provider store={this.state.store}>
<Root />
Expand Down Expand Up @@ -522,4 +526,3 @@ Thanks to Spencer Carli for his awesome article about [Handling Offline actions
### License
MIT
18 changes: 18 additions & 0 deletions src/checkConnectivityInterval.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
5 changes: 0 additions & 5 deletions src/checkInternetAccess.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
/* @flow */

export default function checkInternetAccess(
isConnected: boolean,
timeout: number = 3000,
address: string = 'https://google.com',
): Promise<boolean> {
if (!isConnected) {
return Promise.resolve(false);
}

return new Promise((resolve: (value: boolean) => void) => {
const tm = setTimeout(() => {
resolve(false);
Expand Down
44 changes: 32 additions & 12 deletions src/withNetworkConnectivity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -25,6 +29,7 @@ const withNetworkConnectivity = (
timeout = 3000,
pingServerUrl = 'https://google.com',
withExtraHeadRequest = true,
checkConnectionInterval = 0,
}: Arguments = {},
) => (WrappedComponent: ReactClass<*>) => {
if (typeof withRedux !== 'boolean') {
Expand Down Expand Up @@ -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) => {
Expand All @@ -91,14 +107,18 @@ const withNetworkConnectivity = (
handleConnectivityChange = (isConnected: boolean) => {
const { store } = this.context;
reactConnectionStore.setConnection(isConnected);

// Top most component, syncing with store
if (
typeof store === 'object' &&
typeof store.dispatch === 'function' &&
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: *) => {
Expand Down

0 comments on commit 7374c2b

Please sign in to comment.