🛰 Handling network status with saga channels

A part of proper error handling is to handle network connection status. I am going to show you how to handle changing network connection status with the Navigator.connection API and Redux Saga eventChannel API.

If you know nothing about Redux saga event channels, please check out the documentation first - Redux Saga eventChannel docs.

The objective

Log into console message when application changes its network status, i.e. when it's online or offline.

The solution

  • Create a custom event channel called connectionChannel.
  • Listen on changes from that channel with takeLatest effect, pass those changes to handleConnectionChange callback function.
import { eventChannel } from 'redux-saga';

const Status = {
    ONLINE: true,
    OFFLINE: false,
};

function* createConnectionChannel() {
    // TODO: implement createConnectionChannel saga
}

function* handleConnectionChange(status) {
    switch (status) {
        case Status.OFFLINE:
            console.log(`You're offline, check your internet connection.`);
            break;
        case Status.ONLINE:
            console.log(`Welcome back online!`);
            break;
    }
}

export default function* handleNetworkConnection() {
    const connectionChannel = yield createConnectionChannel();

    yield takeLatest(connectionChannel, handleConnectionChange);
}

Implementing the createConnectionChannel saga

function createConnectionChannel() {
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

    return eventChannel(emit => {
        let prevStatus = null;

        function handleConnectionStatusChange() {
            const status = connection.downlink === 0 ? Status.OFFLINE : Status.ONLINE;

            // The 'change' event is triggered anytime some connection properity changes,
            // but we only care about the network status.
            if (status !== prevStatus) {
                // Sent new network status out of the connectionChannel to the handleConnectionChange
                emit(status);

                prevStatus = status;
            }
        }

        connection.addEventListener('change', handleConnectionStatusChange);

        // Return unsubscribe function
        return () => connection.removeEventListener('change', handleConnectionStatusChange);
    });
}

Unfortunatally, navigator.connection isn't supported everywhere, so we need to fall back to an older API, the online and offline events.

So createConnectionChannel saga is extended as follow:

// ....
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

if (!connection) {
    return eventChannel(emit => {
        function onlineHandler() {
            emit(Status.ONLINE);
        }

        function offlineHandler() {
            emit(Status.OFFLINE);
        }

        window.addEventListener('online', onlineHandler);
        window.addEventListener('offline', offlineHandler);

        return () => {
            window.removeEventListener('online', onlineHandler);
            window.removeEventListener('offline', offlineHandler);
        };
    });
}
// ....

The result

import { sagaEffects, eventChannel, messageActions } from '../../dependencies';

import { Status } from '../../constants';

const { takeLatest, put } = sagaEffects;

function createConnectionChannel() {
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

    if (!connection) {
        return eventChannel(emit => {
            function onlineHandler() {
                emit(Status.ONLINE);
            }

            function offlineHandler() {
                emit(Status.OFFLINE);
            }

            window.addEventListener('online', onlineHandler);
            window.addEventListener('offline', offlineHandler);

            return () => {
                window.removeEventListener('online', onlineHandler);
                window.removeEventListener('offline', offlineHandler);
            };
        });
    }

    return eventChannel(emit => {
        let prevStatus = null;

        function handleConnectionStatusChange() {
            const status = connection.downlink === 0 ? Status.OFFLINE : Status.ONLINE;

            if (status !== prevStatus) {
                emit(status);

                prevStatus = status;
            }
        }

        connection.addEventListener('change', handleConnectionStatusChange);

        return () => connection.removeEventListener('change', handleConnectionStatusChange);
    });
}

function* handleConnectionChange(status) {
    switch (status) {
        case Status.OFFLINE:
            console.log(`You're offline, check your internet connection.`);
            break;
        case Status.ONLINE:
            console.log(`Welcome back online!`);
            break;
    }
}

export default function* handleNetworkConnection() {
    const connectionChannel = yield createConnectionChannel();

    yield takeLatest(connectionChannel, handleConnectionChange);
}

Why using both APIs?

You may be asking, why did I use the first API at all, when the older one can do the trick as well? Well, because for the older API the "online" status does not always mean connection to the internet, it can also just mean connection to some network. By checking the actual downlink value, we can be sure wheter the app has internet connection or not.


Resources

results matching ""

    No results matching ""