Docs Menu
Docs Home
/ /
Atlas Device SDKs
/ /

Handle Sync Errors - React Native SDK

On this page

  • Sync Error Handler
  • Add a Generic Sync Error Handler
  • Handle Compensating Write Errors
  • Handle Client Reset Errors
  • Client Reset Modes
  • Automatic vs. Manual Client Reset
  • Client Reset with Recovery
  • Recover Unsynced Changes Mode
  • Recover or Discard Unsynced Changes Mode
  • Manual Client Reset Fallback
  • Discard Unsynced Changes Mode
  • Discard Unsynced Changes after Breaking Schema Changes
  • Manual Mode
  • Manual Data Recovery
  • Test Client Reset Handling

When you use Atlas Device Sync in your Realm app, you can encounter a new class of errors: sync errors.

The Realm React Native SDK can help you detect and handle sync errors. For example, you can write your own sync error handler to respond to specific errors. You can also define how your client app handles client resets.

You should set an error handler for apps that use Atlas Device Sync. A generic error handler will detect and respond to failed sync-related API calls.

A generic sync error handler is a good way to keep track of sync errors. Using FlexibleSyncConfiguration, you can define your error handling behavior.

To add a generic sync error handler:

  1. Write an error handler function.

  2. Create a FlexibleSyncConfiguration object for your RealmProvider.

  3. Pass your error handler to the onError property of the FlexibleSyncConfiguration object.

const syncConfigWithErrorHandling = {
flexible: true,
onError: (_session, error) => {
console.log(error);
},
};
function RealmWithErrorHandling() {
return (
<RealmProvider sync={syncConfigWithErrorHandling}>
<RestOfApp />
</RealmProvider>
);
}

Tip

For a list of common Device Sync errors and how to handle them, refer to Sync Errors in the App Services Device Sync documentation.

You may want your sync error handler to specifically address compensating write errors in a way that makes sense for your app. The CompensatingWriteError class can help you identify and react to compensating write errors in your custom error handler.

To handle compensating write errors:

  1. Write an error handler function that uses CompensatingWriteError to identify compensating write errors.

  2. Create a FlexibleSyncConfiguration object for your RealmProvider.

  3. Pass your error handler to the onError property of the FlexibleSyncConfiguration object.

export const CompensatingWriteErrorHandling = () => {
const [error, setError] = useState<CompensatingWriteError | undefined>(
undefined,
);
// Create a callback for sync error handling using CompensatingWriteError
const errorCallback: ErrorCallback = (_session, error) => {
if (error instanceof CompensatingWriteError) {
// Handle the compensating write error as needed
console.debug({
code: error.code,
name: error.name,
category: error.category,
message: error.message,
url: error.logUrl,
writes: error.writes,
});
setError(error);
}
};
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
schema={[Person, Turtle]}
sync={{
flexible: true,
onError: errorCallback,
}}>
<CompensatingWriteErrorHandler error={error} />
</RealmProvider>
</UserProvider>
</AppProvider>
);
};

A client reset error is a type of sync error where a client realm cannot sync data with the Atlas App Services backend. Clients in this state may continue to run and save data locally but cannot send or receive sync changesets until they perform a client reset.

To learn about the causes of and modes for handling client resets, refer to Device Sync Client Resets in the App Services documentation.

You can specify which client reset mode your app should use to restore the realm to a syncable state:

  • Recover unsynced changes mode: When you choose this mode, the client attempts to recover unsynced changes. Choose this mode when you do not want to fall through to discard unsynced changes.

  • Recover or discard unsynced changes mode: The client first attempts to recover changes that have not yet synced. If the client cannot recover unsynced data, it falls through to discard unsynced changes but continues to automatically perform the client reset. Choose this mode when you want to enable automatic client recovery to fall back to discard unsynced changes.

  • Discard unsynced changes mode: Restores the realm to a syncable state by discarding changes made since the last sync.

  • Manual recovery mode: Downloads a new copy of the realm, and moves the unsyncable realm to a backup. Migrate unsynced data from the backup copy of the realm to the new syncable copy.

The Realm SDKs provide client reset modes that automatically handle most client reset errors.

Automatic client reset modes restore your local realm file to a syncable state without closing the realm or missing notifications. The following client reset modes support automatic client resets:

  • Recover unsynced changes mode

  • Recover or discard unsynced changes mode

  • Discard unsynced changes mode

The differences between these modes are based on how they handle changes on the device that have not yet synced to the backend. Only manual recovery mode does not perform an automatic client reset.

Choose recover unsynced changes mode to handle most client reset scenarios automatically. This attempts to recover unsynced changes when a client reset occurs.

If your app requires specific client reset logic that can't be handled automatically, you may want or need to add a manual client reset handler to the automatic client reset mode.

New in version realm@10.23.0.

Client Recovery is a feature that is enabled by default when you configure Device Sync. When Client Recovery is enabled, Realm automatically manages the client reset process in most cases. The client can recover unsynced changes when there are no schema changes, or non-breaking schema changes.

To use Client Recovery, configure your realm with one of the following client reset modes:

  • Recover unsynced changes mode

  • Recover or discard unsynced changes

When Client Recovery is enabled, these rules determine how objects are integrated, including how conflicts are resolved when both the backend and the client make changes to the same object:

  • Objects created locally that were not synced before client reset are synced.

  • If an object is deleted on the server, but is modified on the recovering client, the delete takes precedence and the client discards the update.

  • If an object is deleted on the recovering client, but not the server, then the client applies the server's delete instruction.

  • In the case of conflicting updates to the same field, the client update is applied.

For more information about configuring Client Recovery, refer to Client Recovery in the App Services documentation.

Client Recovery cannot succeed when your app makes breaking schema changes. A breaking change is a change that you can make in your server-side schema that requires additional action to handle. In this scenario, client reset falls back to a manual error client reset fallback.

For information on breaking vs. non-breaking schema changes, refer to Breaking vs. Non-Breaking Change Quick Reference in the App Services documentation.

When you choose recover unsynced changes mode, the client attempts to recover unsynced changes with Client Recovery. Choose this mode when you do not want to fall through to discard unsynced changes.

To handle client resets with the recover unsynced changes mode, pass a ClientResetConfiguration to the clientReset field of your FlexibleSyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "recoverUnsyncedChanges".

  • onBefore: Optional. Callback function invoked before the SDK executes this mode, when the SDK receives a client reset error from the backend. Provides a copy of the realm.

  • onAfter: Optional. Callback function invoked after the SDK successfully executes this mode. Provides instances of the realm before and after the client reset.

  • onFallback: Optional. Callback function which the SDK invokes only if the automatic recovery fails. For more information, refer to the Manual Client Reset Fallback section.

The following example implements recover unsynced changes mode:

const syncConfigWithRecoverClientReset = {
flexible: true,
clientReset: {
mode: 'recoverUnsyncedChanges',
onBefore: realm => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
};
function RealmWithRecoverUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithRecoverClientReset}>
<RestOfApp />
</RealmProvider>
);
}
const syncConfigWithRecoverClientReset = {
flexible: true,
clientReset: {
mode: Realm.ClientResetMode.RecoverUnsyncedChanges,
onBefore: realm => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
};
function RealmWithRecoverUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithRecoverClientReset}>
<RestOfApp />
</RealmProvider>
);
}

In recover or discard unsynced changes mode, the client first attempts to recover changes that have not yet synced. If the client cannot recover unsynced data, it falls through to discard unsynced changes but continues to automatically perform the client reset. Choose this mode when you want to enable automatic client recovery to fall back to discard unsynced changes.

Do not use recover or discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To handle client resets with the recover or discard unsynced changes mode, pass a ClientResetConfiguration to the clientReset field of your FlexibleSyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "recoverOrDiscardUnsyncedChanges".

  • onBefore: Optional. Callback function invoked before the SDK executes this mode, when the SDK receives a client reset error from the backend. Provides a copy of the realm.

  • onAfter: Optional. Callback function invoked after the SDK successfully executes this mode. Provides instances of the realm before and after the client reset.

  • onFallback(): Optional. Callback function which the SDK invokes only if both the automatic recovery and and discarding changes fails. For more information, refer to the Manual Client Reset Fallback section.

The following example implements recover unsynced changes mode:

const syncConfigWithRecoverDiscardClientReset = {
flexible: true,
clientReset: {
mode: 'recoverOrDiscardUnsyncedChanges',
onBefore: realm => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
};
function RealmWithRecoverOrDiscardUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithRecoverDiscardClientReset}>
<RestOfApp />
</RealmProvider>
);
}
const syncConfigWithRecoverDiscardClientReset = {
flexible: true,
clientReset: {
mode: Realm.ClientResetMode.RecoverOrDiscardUnsyncedChanges,
onBefore: realm => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
};
function RealmWithRecoverOrDiscardUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithRecoverDiscardClientReset}>
<RestOfApp />
</RealmProvider>
);
}

If the client reset with recovery cannot complete automatically, like when there are breaking schema changes, the client reset process falls through to a manual error handler. This may occur in either of the client reset with recovery modes, recover unsynced changes and recover or discard unsynced changes.

You must provide a manual client reset implementation in the SyncConfiguration.onFallback() callback. onFallback() takes two arguments:

  • session: Session object representing the state of the Device Sync session.

  • path: String with the path to the current realm file.

The following example demonstrates how you can manually handle this error case by discarding all unsynced changes:

let realm; // value assigned in <RestOfApp> with useRealm()
const syncConfigWithClientResetFallback = {
flexible: true,
clientReset: {
mode: 'recoverOrDiscardUnsyncedChanges', // or "recoverUnsyncedChanges"
// can also include `onBefore` and `onAfter` callbacks
onFallback: (_session, path) => {
try {
// Prompt user to perform a client reset immediately. If they don't,
// they won't receive any data from the server until they restart the app
// and all changes they make will be discarded when the app restarts.
const didUserConfirmReset = showUserAConfirmationDialog();
if (didUserConfirmReset) {
// Close and delete old realm from device
realm.close();
Realm.deleteFile(path);
// Perform client reset
Realm.App.Sync.initiateClientReset(app, path);
// Navigate the user back to the main page or reopen the
// the Realm and reinitialize the current page
}
} catch (err) {
// Reset failed. Notify user that they'll need to
// update the app
}
},
},
};
function RealmWithManualClientResetFallback() {
return (
<RealmProvider sync={syncConfigWithClientResetFallback}>
<RestOfApp />
</RealmProvider>
);
}
function RestOfApp() {
// Assigning variable defined above to a realm.
realm = useRealm();
return <>{/* Other components in rest of app */}</>;
}
let realm; // value assigned in <RestOfApp> with useRealm()
const syncConfigWithClientResetFallback = {
flexible: true,
clientReset: {
mode: Realm.ClientResetMode.RecoverOrDiscardUnsyncedChanges, // or "recoverUnsyncedChanges"
// can also include `onBefore` and `onAfter` callbacks
onFallback: (_session, path) => {
try {
// Prompt user to perform a client reset immediately. If they don't,
// they won't receive any data from the server until they restart the app
// and all changes they make will be discarded when the app restarts.
const didUserConfirmReset = showUserAConfirmationDialog();
if (didUserConfirmReset) {
// Close and delete old realm from device
realm.close();
Realm.deleteFile(path);
// Perform client reset
Realm.App.Sync.initiateClientReset(app, path);
// Navigate the user back to the main page or reopen the
// the Realm and reinitialize the current page
}
} catch (err) {
// Reset failed. Notify user that they'll need to
// update the app
}
},
},
};
function RealmWithManualClientResetFallback() {
return (
<RealmProvider sync={syncConfigWithClientResetFallback}>
<RestOfApp />
</RealmProvider>
);
}
function RestOfApp() {
// Assigning variable defined above to a realm.
realm = useRealm();
return <>{/* Other components in rest of app */}</>;
}

New in version realm@10.11.0.

Changed in version realm@10.23.0: Mode renamed from "discardLocal" to "discardUnsyncedChanges". Both currently work, but in a future version, "discardLocal" will be removed. "clientResetBefore" and "clientResetAfter" callbacks renamed to "onBefore" and "onAfter", respectively.

Discard Unsynced Changes mode permanently deletes all local unsynced changes made since the last successful sync. You might use this mode when your app requires client recovery logic that is not consistent with automatic Client Recovery, or when you don't want to recover unsynced data.

Do not use discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To handle client resets with the discard unsynced changes mode, pass a ClientResetConfiguration to the clientReset field of your FlexibleSyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "discardUnsyncedChanges".

  • onBefore: Optional. Callback function invoked before the SDK executes this mode, when the SDK receives a client reset error from the backend. Provides a copy of the realm.

  • onAfter: Optional. Callback function invoked after the SDK successfully executes this mode. Provides instances of the realm before and after the client reset.

The following example implements discard unsynced changes mode:

const syncConfigWithDiscardClientReset = {
flexible: true,
clientReset: {
mode: 'discardUnsyncedChanges',
onBefore: realm => {
console.log('Beginning client reset for ', realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
console.log('Finished client reset for', beforeRealm.path);
console.log('New realm path', afterRealm.path);
},
},
};
function RealmWitDiscardUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithDiscardClientReset}>
<RestOfApp />
</RealmProvider>
);
}
const syncConfigWithDiscardClientReset = {
flexible: true,
clientReset: {
mode: Realm.ClientResetMode.DiscardUnsyncedChanges,
onBefore: realm => {
console.log('Beginning client reset for ', realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
console.log('Finished client reset for', beforeRealm.path);
console.log('New realm path', afterRealm.path);
},
},
};
function RealmWitDiscardUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithDiscardClientReset}>
<RestOfApp />
</RealmProvider>
);
}

If your application experiences a breaking schema change, discard unsynced changes mode cannot handle the resulting client reset automatically. Instead, you must provide a manual client reset implementation in the SyncConfiguration error() callback. The following example demonstrates how you can manually handle this error case by discarding all unsynced changes:

// Once you have opened your Realm, you will have to keep a reference to it.
// In the error handler, this reference is called `realm`
async function handleSyncError(session, syncError) {
if (syncError.name == 'ClientReset') {
console.log(syncError);
try {
console.log('error type is ClientReset....');
const path = realm.path; // realm.path will not be accessible after realm.close()
realm.close();
Realm.App.Sync.initiateClientReset(app, path);
// Download Realm from the server.
// Ensure that the backend state is fully downloaded before proceeding,
// which is the default behavior.
realm = await Realm.open(config);
realm.close();
} catch (err) {
console.error(err);
}
} else {
// ...handle other error types
}
}
const syncConfigWithDiscardAfterBreakingSchemaChanges = {
flexible: true,
clientReset: {
mode: 'discardUnsyncedChanges',
onBefore: realm => {
// NOT used with destructive schema changes
console.log('Beginning client reset for ', realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
// Destructive schema changes do not hit this function.
// Instead, they go through the error handler.
console.log('Finished client reset for', beforeRealm.path);
console.log('New realm path', afterRealm.path);
},
},
onError: handleSyncError, // invoked with destructive schema changes
};
function RealmWitDiscardAfterBreakingSchemaChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithDiscardAfterBreakingSchemaChanges}>
<RestOfApp />
</RealmProvider>
);
}
// Once you have opened your Realm, you will have to keep a reference to it.
// In the error handler, this reference is called `realm`
async function handleSyncError(session, syncError) {
if (syncError.name == 'ClientReset') {
console.log(syncError);
try {
console.log('error type is ClientReset....');
const path = realm.path; // realm.path will not be accessible after realm.close()
realm.close();
Realm.App.Sync.initiateClientReset(app, path);
// Download Realm from the server.
// Ensure that the backend state is fully downloaded before proceeding,
// which is the default behavior.
realm = await Realm.open(config);
realm.close();
} catch (err) {
console.error(err);
}
} else {
// ...handle other error types
}
}
const syncConfigWithDiscardAfterBreakingSchemaChanges = {
flexible: true,
clientReset: {
mode: Realm.ClientResetMode.DiscardUnsyncedChanges,
onBefore: realm => {
// NOT used with destructive schema changes
console.log('Beginning client reset for ', realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
// Destructive schema changes do not hit this function.
// Instead, they go through the error handler.
console.log('Finished client reset for', beforeRealm.path);
console.log('New realm path', afterRealm.path);
},
},
onError: handleSyncError, // invoked with destructive schema changes
};
function RealmWitDiscardAfterBreakingSchemaChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithDiscardAfterBreakingSchemaChanges}>
<RestOfApp />
</RealmProvider>
);
}

Note

Discard with Recovery

If you'd like to attempt to recover unsynced changes, but but discard any changes that cannot be recovered, refer to the recover or discard unsynced changes mode section.

Changed in version realm@10.23.0: onManual callback added

In manual mode, you define your own client reset handler. You might want to use a manual client reset handler if the Automatic Recovery logic does not work for your app and you can't discard unsynced local data.

To handle client resets with manual mode, pass a ClientResetConfiguration to the clientReset field of your FlexibleSyncConfiguration. Include these properties in the ClientResetConfiguration:

  • mode: Set to "manual".

  • onManual: Optional. Callback function invoked when the client reset occurs. Provides information about the sync session and the path to the current realm. If you don't set the onManual error handler, the client reset error falls back to the general sync error handler.

const syncConfigWithManualClientReset = {
flexible: true,
clientReset: {
mode: 'manual',
onManual: (session, path) => {
// handle manual client reset here
},
},
};
function RealmWitManualClientReset() {
return (
<RealmProvider sync={syncConfigWithManualClientReset}>
<RestOfApp />
</RealmProvider>
);
}
const syncConfigWithManualClientReset = {
flexible: true,
clientReset: {
mode: 'manual',
onManual: (session, path) => {
// handle manual client reset here
},
},
};
function RealmWitManualClientReset() {
return (
<RealmProvider sync={syncConfigWithManualClientReset}>
<RestOfApp />
</RealmProvider>
);
}

To recover data from a manual client reset requires significant amounts of code, schema concessions, and custom conflict resolution logic. If you need to implement your own custom client reset logic, see the Advanced Guide to Manual Client Reset Data Recovery.

You can manually test your application's client reset handling by terminating and re-enabling Device Sync.

When you terminate and re-enable Sync, clients that have previously connected with Sync are unable to connect until after they perform a client reset. Terminating Sync deletes the metadata from the server that allows the client to synchronize. The client must download a new copy of the realm from the server. The server sends a client reset error to these clients. So, when you terminate Sync, you trigger the client reset condition.

To test client reset handling:

  1. Write data from a client application and wait for it to synchronize.

  2. Terminate and re-enable Device Sync.

  3. Run the client app again. The app should get a client reset error when it tries to connect to the server.

Warning

While you iterate on client reset handling in your client application, you may need to terminate and re-enable Sync repeatedly. Terminating and re-enabling Sync renders all existing clients unable to sync until after completing a client reset. To avoid this in production, test client reset handling in a development environment.

Back

Manage Sync Session