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.
| Feature | Purpose |
|---|---|
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 |
unlimitedRetryCheckInternet | Combines 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
In web environments, the default is checking the availability of the internet using
navigator.onLine. 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. To that end, you must override the hasInternet method of your
base action, and then provide your own logic to determine if the internet is available or not.
Alternatives are using the NetInfo package for React Native, or using the is-online package for Node.js and the browser.
For an example using NetInfo, first add it to your package.json:
"dependencies": {
"@react-native-community/netinfo": "^11.4.1"
}
Then, import it in your base action, and override the hasInternet() method:
import NetInfo from '@react-native-community/netinfo';
export abstract class Action extends KissAction<State> {
protected hasInternet(): Promise<boolean> {
return NetInfo.fetch().then(state => state.isConnected ?? true);
}
}
The same hasInternet() method 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
-
checkInternetcan be combined with retry. But sincebefore()is not retried, the internet is only checked once. SoretrypluscheckInternetdoesn'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:
| Parameter | Default | Description |
|---|---|---|
initialDelay | 350 ms | Delay before the first retry |
multiplier | 2 | Factor by which delay increases each retry |
maxDelay | 5 sec | Maximum delay when retrying after errors (with internet) |
maxDelayNoInternet | 1 sec | Maximum 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
beforemethod throws an error, retries will not happen. -
An action that aborts itself by throwing an
AbortDispatchExceptionis not retried. -
Calling
store.clearInternalActionProps()(for example, on logout), or shutting down the store withstore.setShutDown(true), stops the retries. The action is then aborted with anAbortDispatchException, so it doesn't fail. -
It can't be combined with
retry,checkInternetornonReentrant, since it already does what they do. It can't be combined withdebounce,throttle,fresh,ignoreOld,sequentialor polling either, nor used in the optimistic actions. Doing so makes the dispatch throw aStoreException. When polling, add it to the action returned bycreatePollingAction()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.
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.
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().