Search code examples
javascriptreact-nativereduxredux-thunk

How to use Redux in react-native to dispatch NetInfo connectivy change action?


I'd like to be able to 'continuosly' monitor device connection in my react-native app.

I'm already using redux-thunk and redux with success.

Actually I'me able to do this

import { NetInfo } from 'react-native';

import {
    CONNECTION_CHECKING,
    CONNECTION_AVAILABLE,
    CONNECTION_UNAVAILABLE, 
} from './types';


export const connectionChecking = () => {
    return (dispatch) => {

        dispatch ({
            type: CONNECTION_CHECKING
        });

        NetInfo.getConnectionInfo()
            .then((connectionInfo) => {

                console.log('Connection info: ', connectionInfo);
                switch (connectionInfo.type) {
                    case 'cellular':
                        dispatch ({
                            type: CONNECTION_AVAILABLE,
                            payload: connectionInfo.type
                        });
                        break;
                    case 'wifi':
                        dispatch ({
                            type: CONNECTION_AVAILABLE,
                            payload: connectionInfo.type
                        });
                        break;
                    default:
                        dispatch ({
                            type: CONNECTION_UNAVAILABLE,
                        });
                }
            })
            .catch((error) => console.warn(error));

    };
};

But, I don't know how to dispatch new action when connection changes..

From the react-native doc i see this:

NetInfo.addEventListener(
  'connectionChange',
  handleConnectivityChange
);

But ... how to dispatch an action inside an event listener?

I tried to add this into the same action file

NetInfo.addEventListener(
    'connectionChange', (connectionInfo)  => {
        return (dispatch) => {

            console.log('Connection info changed ', connectionInfo);
            switch (connectionInfo.type) {
                case 'cellular':
                case 'wifi':
                    dispatch ({
                        type: CONNECTION_AVAILABLE,
                        payload: connectionInfo.type
                    });
                    break;
                default:
                    dispatch ({
                        type: CONNECTION_UNAVAILABLE,
                    });
            }

        };
    }
);

I got no results, no console.log, and so I think it's the wrong way


Solution

  • You just need to set up your event listener somewhere with access to the dispatch function.

    Directly from a component

    The easiest thing to do (allowing for clean unsubscribing) is to set them up in your topmost connected component:

    class MyApp extends Component {
      constructor(props) {
        super(props);
    
        // Dispatch an action from your event handler (and do whatever else)
        this._handleConnectionChange = (connectionInfo) => props.dispatch({ 
          type: 'CONNECTION_CHANGE', 
          connectionInfo
        });
    
        NetInfo.addEventListener('connectionChange', this._handleConnectionChange);
      }
    
      componentWillUnmount() {
        NetInfo.removeEventListener('connectionChange', this._handleConnectionChange);
      }
    }
    
    export default connect()(MyApp); // MyApp must be connected
    

    A little abstraction - adding the listener within a Thunk

    Personally I like to wrap this kind of thing in a thunk to avoid listener handling cluttering my components. The important point here is that thunks, like connected components, have access to dispatch, so by defining the listener within the inner part of a thunk we can dispatch actions (or more thunks) in response to changes.

    // connectionActions.js
    
    let handleConnectionChange;
    
    export function registerListeners() {
      return (dispatch) => {
        handleConnectionChange = (connectionInfo) => {
          dispatch(connectionChanged(connectionInfo));
        }
        NetInfo.addEventListener('connectionChange', handleConnectionChange);
      }
    }
    
    export function unregisterListeners() {
      return (dispatch) => {
        handleConnectionChange && NetInfo.removeEventListener('connectionChange', handleConnectionChange);
      }
    }
    
    function connectionChanged(connectionInfo) {
      return (dispatch) => {
        switch (connectionInfo.type) {
          case 'cellular':
            dispatch({
              type: CONNECTION_AVAILABLE,
              payload: connectionInfo.type
            });
            break;
          // ...Other cases
        }
      }
    }
    

    And then you can dispatch registerListeners() and unregisterListeners() from MyApp rather than defining the details of the listeners there:

    // app.js
    // ...other imports
    import * as connectionActions from './connectionActions'
    
    class MyApp extends Component {
      constructor(props) {
        super(props);
        // Dispatch your thunk to take care of the registration
        props.dispatch(connectionActions.registerListeners());
      }
    
      componentWillUnmount() {
        this.props.dispatch(connectionActions.unregisterListeners());
      }
    }
    
    export default connect()(MyApp)
    

    A bit more abstraction - a higher-order component for listening

    In apps where I have multiple possible top level components (eg different build variants with different homepages, or a Login screen that's not part of the main navigator), I like to take this kind of boilerplate and wrap it in a higher order component:

    // hoc/withNetListeners.js
    
    export default function withNetListeners(SourceComponent): {
      class HOC extends React.Component {
        constructor(props) {
          super(props);
          props.dispatch(connectionActions.registerListeners());
        }
    
        componentWillUnmount() {
          this.props.dispatch(connectionActions.unregisterListeners());
        }
    
        render() {
          return <SourceComponent {...this.props} />;
        }
      }
      return hoistStatics(HOC, SourceComponent); // Package hoist-non-react-statics
    }
    

    And then in whichever component(s) act as your root(s):

    // app.js
    class MyApp extends Component {
      // ... Just your UI, no listeners ...
    }
    
    // Inject the listeners with a HOC. Make you `connect` last, because 
    // `withNetListeners` needs to be given access to `dispatch`.
    export default connect()(withNetListeners(MyApp))