Skip to main content

Internet features

The features explained in this page help us check for internet connectivity before running an action. See all features, and which ones can be combined, in Action features.

FeaturePurpose
checkInternet = { dialog: true }Checks internet before action; shows dialog if no connection
checkInternet = { dialog: false }Checks internet before action; fails without a dialog
checkInternet = { abort: true }Checks internet before action; aborts silently if no connection
unlimitedRetryCheckInternetCombines internet check + unlimited retry + non-reentrant

checkInternet​

Adding checkInternet = { dialog: true } to your action ensures it only runs with internet. If there is no internet, the action fails, and an error dialog opens with the title "There is no Internet" and the message "Please, verify your connection."

class LoadPrices extends Action {

checkInternet = { dialog: true }

async reduce() { ... }
}

Use checkInternet = { dialog: false } if you don't want to open a dialog. The action still fails, so you can display some information in your components instead:

function MyComponent() {
const isFailed = useIsFailed(LoadPrices);

return (
<div>
{isFailed ? <p>No Internet connection</p> : null}
</div>
);
};

Or use the exception, which is a UserException:

function MyComponent() {
const isFailed = useIsFailed(LoadPrices);
const exception = useExceptionFor(LoadPrices);

return (
<div>
{isFailed ? <p>{exception?.message ?? 'No Internet'}</p> : null}
</div>
);
};

Note: checkInternet only checks if the device's internet is on or off. It does not verify if the internet provider is working or if the server is available. The check may pass, but network requests can still fail.

Checking the internet connection​

By default, the connection checked is the online signal of the store, store.isOnline (see Where "focused" and "online" come from). In the browser, that's navigator.onLine, following the online and offline events. However, navigator.onLine is not very useful, as it only tells you if there's a local connection, and not whether the internet is accessible. Also, this only really works for the web. In other environments (Node.js, React Native) the default assumes there is internet.

In other words, to make checkInternet work properly you will need to provide your own logic to check for internet availability. The recommended way is giving the store an onlineSource, which also feeds useIsOnline() and the onOnline callback of useStoreEffect. For example, with the NetInfo package for React Native, first add it to your package.json:

"dependencies": {
"@react-native-community/netinfo": "^11.4.1"
}

Then, give the store an onlineSource that listens to it:

import NetInfo from '@react-native-community/netinfo';

const store = createStore<State>({
initialState: State.initialState,
onlineSource: (setOnline) => NetInfo.addEventListener((s) => setOnline(s.isConnected !== false)),
});

Note isConnected is null while NetInfo doesn't know yet. The !== false counts that as online, so the action still tries. That's safer: a wrong "online" only costs a request that fails, while a wrong "offline" would block a request that could have worked.

The source is started when the signal is first read (for example, by the first action with checkInternet), and stopped by store.dispose(). A server store (created with server: true, for server-side rendering) never starts it: there, the app always counts as online.

Alternatively, override the hasInternet() method of your base action, to check the connection on demand each time an action needs it (for example, with the is-online package for Node.js and the browser, or by pinging your own server). The override then ignores store.isOnline:

export abstract class Action extends KissAction<State> {

protected hasInternet(): Promise<boolean> {
return NetInfo.fetch().then(state => state.isConnected !== false);
}
}

The same check is also used by unlimitedRetryCheckInternet.

Customizing the error​

The internet check runs in the default before() method of the action. To show a different error, override before(), call super.before(), and throw your own UserException if it fails:

class LoadPrices extends Action {

checkInternet = { dialog: true }

async before() {
try {
await super.before();
} catch (error) {
throw new UserException('You are offline. The prices could not be loaded.');
}
}

async reduce() { ... }
}

In the same way, if you override before() for any other reason, you must call super.before() (and await it), or the internet won't be checked.

Combining with other features​

  • checkInternet can be combined with retry. But since before() is not retried, the internet is only checked once. So retry plus checkInternet doesn't retry when there is no internet. It only retries if there IS internet, but the action fails for some other reason. To retry until there is internet, use unlimitedRetryCheckInternet instead.

  • It can be combined with sequential. The internet is then checked when the action gets its turn in the queue.

  • It can be combined with OptimisticCommand, OptimisticSync and OptimisticSyncWithPush. If there is no internet, the optimistic value is not applied, and nothing is sent to the server.

  • It can't be combined with unlimitedRetryCheckInternet, which already checks the internet, nor used in a ServerPush. Doing so makes the dispatch throw a StoreException.

  • When polling, add it to the action returned by createPollingAction(), not to the action that starts and stops the polling. See polling.

Aborting when there is no internet​

Use checkInternet = { abort: true } to abort the action silently when there is no internet, as if it had never been dispatched. No errors are thrown, and no dialogs are shown.

class LoadPrices extends Action {

checkInternet = { abort: true }

async reduce() { ... }
}

Technically, the action throws an AbortDispatchException, so it doesn't fail, and its status has isDispatchAborted: true. This means useIsFailed(LoadPrices) returns false.

You can't set both dialog and abort: true. Doing so makes the dispatch throw a StoreException.

Note: This only checks if the device's internet is on or off. It does not verify if the internet provider is working or if the server is available. The check may pass, but network requests can still fail.

unlimitedRetryCheckInternet​

Adding unlimitedRetryCheckInternet = true to your action retries it indefinitely, until it succeeds. It checks for internet connectivity before each attempt, and waits if there is no connection. It also retries if there is internet, but the action fails for other reasons.

This is useful for critical actions that must eventually succeed, such as loading essential data when your app opens.

class LoadInitialData extends Action {

unlimitedRetryCheckInternet = true

async reduce() {
let response = await fetch('https://example.com/initialData');
let data = await response.json();
return (state: State) => state.copy({ data });
}
}

It works like retry with unlimited retries, plus checkInternet, plus nonReentrant, but with one difference: Combining retry with checkInternet will not retry when there is no internet, while unlimitedRetryCheckInternet will.

It only works with async reducers. If the reducer is sync, the dispatch fails with a StoreException.

Parameters​

You can change one or more of these parameters to customize the retries. Changing them also turns the feature on:

ParameterDefaultDescription
initialDelay350 msDelay before the first retry
multiplier2Factor by which delay increases each retry
maxDelay5 secMaximum delay when retrying after errors (with internet)
maxDelayNoInternet1 secMaximum delay when waiting for internet
class LoadInitialData extends Action {

unlimitedRetryCheckInternet = {
initialDelay: 350, // Delay in milliseconds before the first retry
multiplier: 2, // Factor used to increase the delay after each retry
maxDelay: 5000, // Maximum delay between retries, when the action fails
maxDelayNoInternet: 1000, // Maximum delay between retries, when there is no internet
}

async reduce() { ... }
}

The shorter maxDelayNoInternet allows faster recovery when connectivity returns. A multiplier of 1 keeps the delay constant. Invalid values (for example, a multiplier below 1, or a negative delay) make the dispatch throw a StoreException that explains the problem.

Non-reentrant​

The action is non-reentrant: while it is running, dispatching another one with the same non-reentrant key is aborted silently. Since it keeps retrying until it succeeds, this lasts the whole time from when the action is dispatched until it succeeds, including the waits between retries.

As with nonReentrant, the key is based on the action class by default. Override nonReentrantKeyParams() so that actions of the same class, but with different parameters, don't block each other. Or override computeNonReentrantKey() so that different action classes share the same key:

class LoadUser extends Action {
constructor(readonly userId: string) { super(); }

unlimitedRetryCheckInternet = true

nonReentrantKeyParams() { return this.userId; }

async reduce() { ... }
}

Tracking retry attempts​

Use this.attempts to check the current attempt number. It's 0 in the first attempt, 1 in the first retry, and so on. Attempts that found no internet count too.

class LoadInitialData extends Action {

unlimitedRetryCheckInternet = true

async reduce() {
console.log('Attempt number: ' + this.attempts);
...
}
}

Logging retries​

All retries are logged with Store.log, including the action, the attempt number, and whether the problem was no internet. To send the logs somewhere else, or to turn them off, use the logger parameter when you create the store:

const store = createStore<State>({
initialState: new State(),
logger: null, // Turns off logging
});

Notes​

  • If you await dispatchAndWait(action), it only finishes when the action succeeds. This may take a long time (or never happen) if there is no internet, or if the action keeps failing.

  • If your before method throws an error, retries will not happen.

  • An action that aborts itself by throwing an AbortDispatchException is not retried.

  • Calling store.clearInternalActionProps() (for example, on logout), or shutting down the store with store.setShutDown(true), stops the retries. The action is then aborted with an AbortDispatchException, so it doesn't fail.

  • It can't be combined with retry, checkInternet or nonReentrant, since it already does what they do. It can't be combined with debounce, throttle, fresh, ignoreOld, sequential or polling either, nor used in the optimistic actions. Doing so makes the dispatch throw a StoreException. When polling, add it to the action returned by createPollingAction() instead.

  • This only checks if the device's internet is on or off. It does not verify if the internet provider is working or if the server is available. The check may pass, but network requests can still fail.

  • In a server store (created with server: true, the store that loads the data of a request in server-side rendering), the internet is not checked (the server counts as online), the reducer runs once, and if it throws, the action fails with that error right away.

Simulating the connection in tests​

During tests, you can simulate the internet being on or off. This works for all actions that use checkInternet or unlimitedRetryCheckInternet, and the simulation takes precedence over the hasInternet() method, even if you override it. (You can also set the online signal of the store with store.setOnline(false), which affects the actions, useIsOnline() and the onOnline/onOffline callbacks alike, but the simulation below takes precedence over it.)

Option 1: Set forceInternetOnOffSimulation on the store to affect all actions:

store.forceInternetOnOffSimulation = () => false; // Simulates NO internet for all actions

Return true to simulate having internet, false to simulate no internet, or null to use the real connectivity status (the default). Since it's tied to the store, it resets automatically when the store is recreated.

Option 2: Override the internetOnOffSimulation getter in a single action:

class LoadPrices extends Action {

checkInternet = { dialog: false }

get internetOnOffSimulation(): boolean | null {
return false; // Simulates NO internet
}

async reduce() { ... }
}

By default, internetOnOffSimulation returns the value of store.forceInternetOnOffSimulation().