Description
Before opening, please confirm:
- I have searched for duplicate or closed issues and discussions.
- I have read the guide for submitting bug reports.
- I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
React Native
Amplify APIs
GraphQL API, DataStore
Amplify Categories
auth, api
Environment information
System:
OS: Linux 6.4 Arch Linux
CPU: (16) x64 AMD Ryzen 7 3700X 8-Core Processor
Memory: 10.35 GB / 31.26 GB
Container: Yes
Shell: 5.9 - /bin/zsh
Binaries:
Node: 16.13.2 - ~/.nvm/versions/node/v16.13.2/bin/node
Yarn: 1.22.19 - /usr/bin/yarn
npm: 8.1.2 - ~/.nvm/versions/node/v16.13.2/bin/npm
npmPackages:
@aws-amplify/datastore-storage-adapter: ^2.0.42 => 2.0.42
@aws-amplify/ui-react-native: ^1.2.20 => 1.2.20
@azure/core-asynciterator-polyfill: ^1.0.2 => 1.0.2
@babel/core: ^7.20.0 => 7.22.9 (7.9.0)
@expo/webpack-config: ^18.0.1 => 18.1.1
@react-native-async-storage/async-storage: 1.17.11 => 1.17.11
@react-native-community/netinfo: 9.3.7 => 9.3.7
@react-navigation/bottom-tabs: ^6.5.8 => 6.5.8
@react-navigation/material-bottom-tabs: ^6.2.16 => 6.2.16
@react-navigation/native-stack: ^6.9.13 => 6.9.13
@reduxjs/toolkit: ^1.9.5 => 1.9.5
@reduxjs/toolkit-query: 1.0.0
@reduxjs/toolkit-query-react: 1.0.0
@testing-library/jest-native: ^5.4.2 => 5.4.2
@testing-library/react-native: ^12.1.3 => 12.1.3
@types/jest: ^29.5.3 => 29.5.3
@types/react: ~18.0.14 => 18.0.38
HelloWorld: 0.0.1
amazon-cognito-identity-js: ^6.3.1 => 6.3.1
amazon-cognito-identity-js/internals: undefined ()
aws-amplify: ^5.3.6 => 5.3.6
core-js: ^3.31.1 => 3.31.1
experiments-app: 1.0.0
expo: ~48.0.18 => 48.0.20
expo-blur: ~12.2.2 => 12.2.2
expo-clipboard: ~4.1.2 => 4.1.2
expo-file-system: ~15.2.2 => 15.2.2 (15.3.0, 11.1.3)
expo-haptics: ~12.2.1 => 12.2.1
expo-sqlite: ~11.1.1 => 11.1.1
expo-status-bar: ~1.4.4 => 1.4.4
faker: 5.5.3 => 5.5.3
jest-expo: ^49.0.0 => 49.0.0
mock-async-storage: ^2.2.0 => 2.2.0
moment: ^2.29.4 => 2.29.4
moment-timezone: ^0.5.43 => 0.5.43
react: 18.2.0 => 18.2.0
react-content-loader: ^6.2.1 => 6.2.1
react-content-loader/native: undefined ()
react-native: 0.71.8 => 0.71.8
react-native-dotenv: ^3.4.9 => 3.4.9
react-native-gesture-handler: ~2.9.0 => 2.9.0
react-native-get-random-values: ~1.8.0 => 1.8.0
react-native-hold-menu: ^0.1.6 => 0.1.6
react-native-paper: ^5.9.1 => 5.9.1
react-native-paper-dates: ^0.18.12 => 0.18.12
react-native-reanimated: ~2.14.4 => 2.14.4
react-native-safe-area-context: 4.5.0 => 4.5.0
react-native-screens: ~3.20.0 => 3.20.0
react-native-svg: 13.4.0 => 13.4.0
react-native-testing-library-website: 0.0.0
react-native-unimodules: ^0.14.10 => 0.14.10
react-native-url-polyfill: ^2.0.0 => 2.0.0 (1.3.0)
react-native-web: ~0.18.10 => 0.18.12
react-navigation-example: 0.0.1
react-redux: ^8.1.1 => 8.1.1
redux-example: 0.0.1
redux-saga: ^1.2.3 => 1.2.3
redux-saga/effects: undefined ()
typescript: ^4.9.4 => 4.9.5
npmGlobalPackages:
@aws-amplify/cli: 11.0.3
@bubblewrap/cli: 1.18.1
@ionic/cli: 6.20.3
amplify-cli: 1.0.0
cordova: 11.1.0
corepack: 0.10.0
create-react-native-app: 3.9.0
deadfile: 2.0.1
docsify-cli: 4.4.4
graphql-language-service-cli: 3.3.16
jsonminify: 0.4.2
mjson: 0.4.2
native-run: 1.7.1
npm: 8.1.2
prebuild-install: 7.1.1
react_app: 0.1.0
react-dom: 17.0.2
react-js-to-ts: 1.4.0
react: 18.2.0
serve: 14.2.0
ts-node: 10.9.1
typescript: 4.8.2
uglify-js: 3.17.4
uglifyjs: 2.4.11
Describe the bug
I would like to modify a record before deleting it with DataStore. In my case, I have a Comment model, and I would like to strip the body before deleting it. I am using optimistic concurrency.
If I only delete the record, everything works fine. If I modify the record before deleting it, I get conflict errors that are never resolved.
On the web version of my app, I was able to work around this issue by returning the remote model in the conflict resolver. This is using amplify 4.3.46. I'm now developing a mobile version with amplify 5.3.6 using the same backend. The workaround no longer seems to work, and DataStore continually tries to send the mutation.
However the data on the backend does seem to update successfully as reflected in dynamodb.
I thought a better solution to this problem would be to instead delete the body during the delete mutation with a custom override, but I wasn't able to figure out how to do it.
I'm not able to look at the actual network data as it seems to be quite difficult in React Native, but here are the responses I see in the web version, which complete successfully after one failed attempt:
Expected behavior
I would expect datastore to not continually try to send mutations after deleting a record.
Reproduction steps
- create a project with expo
- npx expo install @aws-amplify/datastore-storage-adapter @aws-amplify/ui-react-native @azure/core-asynciterator-polyfill @react-native-async-storage/async-storage amazon-cognito-identity-js expo-sqlite
- use Authenticator component
- initialise amplify
- create a graphql API with DataStore enabled and select optimistic concurrency
Code Snippet
My model in the schema:
type Comment
@auth(rules: [
{allow: groups, groups: ["USER"], operations: [read]},
{allow: owner, operations: [create, read, delete, update]},
{allow: groups, groups: ["ADMIN"], operations: [create, read, delete, update]},
])
@model {
id: ID!
parentId: ID @index(name: "byParent")
owner: String
@auth(rules: [
{allow: groups, groups: ["USER"], operations: [read]},
{allow: owner, operations: [create, read, delete]},
{allow: groups, groups: ["ADMIN"], operations: [create, read, delete]},
])
tenantId: ID! @index(name: "byTenantId")
@auth(rules: [
{allow: groups, groups: ["USER"], operations: [read]},
{allow: owner, operations: [create, read, delete]},
{allow: groups, groups: ["ADMIN"], operations: [create, read, delete]},
])
body: String
author: User @belongsTo
visibility: CommentVisibility
archived: Int @default(value: "0") @index(name: "byArchived")
@auth(rules: [
{allow: groups, groups: ["USER"], operations: [read]},
{allow: owner, operations: [create, read, delete]},
{allow: groups, groups: ["ADMIN"], operations: [create, read, delete]},
])
}
App.tsx
import * as React from "react";
import "@azure/core-asynciterator-polyfill";
import { DataStore } from "aws-amplify";
import { ExpoSQLiteAdapter } from "@aws-amplify/datastore-storage-adapter/ExpoSQLiteAdapter";
import { Authenticator } from "@aws-amplify/ui-react-native";
import { Amplify } from "aws-amplify";
import config from "./src/aws-exports";
Amplify.configure(config);
DataStore.configure({
storageAdapter: ExpoSQLiteAdapter,
});
const App = () => {
return (
<Authenticator.Provider>
<Authenticator loginMechanisms={["email"]}>
<Main />
</Authenticator>
</Authenticator.Provider>
);
};
export default App;
deleteComment function
const deleteComment = async () => {
try {
if (selectedComment) {
const existingComment = await DataStore.query(
models.Comment,
selectedComment.id
);
if (existingComment) {
// if this part is removed, everything works
const updated = await DataStore.save(
models.Comment.copyOf(existingComment, (upd) => {
upd.body = "";
})
);
await DataStore.delete(updated);
}
}
} catch (e) {
console.log(e);
}
};
Conflict resolver:
import { DISCARD } from "@aws-amplify/datastore";
import {
SyncConflict,
PersistentModel,
PersistentModelConstructor,
} from "@aws-amplify/datastore";
import * as models from "../../models";
import determineTaskStatus from "../../utilities/determineTaskStatus";
const dataStoreConflictHandler = async (
conflict: SyncConflict
): Promise<symbol | PersistentModel> => {
const { modelConstructor, localModel, remoteModel } = conflict;
console.log(
"DataStore has found a conflict",
modelConstructor,
remoteModel,
localModel
);
if (
modelConstructor ===
(models.Comment as PersistentModelConstructor<models.Comment>)
) {
return remoteModel;
}
return DISCARD;
};
export default dataStoreConflictHandler;
DataStore.configure (run in redux-saga)
yield call([DataStore, DataStore.configure], {
errorHandler: (err) => {
console.log("DataStore error:", err);
console.log("Cause:", err.cause);
},
syncExpressions: [
...modelsToSync.map((model) =>
syncExpression(
model,
() => (m) => m.tenantId.eq(tenantId)
)
),
...archivedModels.map((model) =>
syncExpression(
model,
() => (m) =>
m.and((m) => [
m.tenantId.eq(tenantId),
m.archived.eq(0),
])
)
),
syncExpression(
models.Tenant,
() => (m) => m.id.eq(tenantId)
),
],
conflictHandler: dataStoreConflictHandler,
});
Log output
This is the error I see when immediately trying to delete a comment:
LOG DataStore has found a conflict [Function Comment] {"_deleted": null, "_lastChangedAt": 1691010033913, "_version": 2, "archived": 0, "body": "", "createdAt": "2023-08-02T21:00:29.636Z", "id": "f373b642-6982-4ed1-8029-c2aff869b0c1", "owner": null, "parentId": "1f40c987-c109-4b7d-8b1a-188080f86f3d", "tenantId": "7b18c148-6259-4adf-948b-257756e6eb4e", "updatedAt": "2023-08-02T21:00:33.878Z", "userCommentsId": undefined, "visibility": "EVERYONE"} {"_deleted": undefined, "_lastChangedAt": undefined, "_version": 1, "archived": null, "body": null, "createdAt": null, "id": "f373b642-6982-4ed1-8029-c2aff869b0c1", "owner": null, "parentId": null, "tenantId": null, "updatedAt": null, "visibility": null}
LOG DataStore error: {"cause": [Error: RetryMutation], "errorType": "Unknown", "localModel": null, "message": "RetryMutation", "model": "Comment", "operation": undefined, "process": "sync", "recoverySuggestion": "Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues", "remoteModel": null}
LOG Cause: [Error: RetryMutation]
On reloading the app I see the same error:
LOG DataStore has found a conflict [Function Comment] {"_deleted": true, "_lastChangedAt": 1691010034378, "_version": 3, "archived": 0, "body": "", "createdAt": "2023-08-02T21:00:29.636Z", "id": "f373b642-6982-4ed1-8029-c2aff869b0c1", "owner": null, "parentId": "1f40c987-c109-4b7d-8b1a-188080f86f3d", "tenantId": "7b18c148-6259-4adf-948b-257756e6eb4e", "updatedAt": "2023-08-02T21:00:33.878Z", "userCommentsId": undefined, "visibility": "EVERYONE"} {"_deleted": undefined, "_lastChangedAt": undefined, "_version": 1, "archived": null, "body": null, "createdAt": null, "id": "f373b642-6982-4ed1-8029-c2aff869b0c1", "owner": null, "parentId": null, "tenantId": null, "updatedAt": null, "visibility": null}
LOG DataStore error: {"cause": [Error: RetryMutation], "errorType": "Unknown", "localModel": null, "message": "RetryMutation", "model": "Comment", "operation": undefined, "process": "sync", "recoverySuggestion": "Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues", "remoteModel": null}
LOG Cause: [Error: RetryMutation]
and this repeats every time I load up the app.
I tried to include a debug log, but it was too long and GitHub rejected the issue.
aws-exports.js
/* eslint-disable */
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.
const awsmobile = {
"aws_project_region": "eu-west-1",
"aws_appsync_graphqlEndpoint": "https://lwmusnla5bfhrjmqhjm7miauxa.appsync-api.eu-west-1.amazonaws.com/graphql",
"aws_appsync_region": "eu-west-1",
"aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
"aws_cognito_identity_pool_id": "eu-west-1:7b0db40b-57bf-4438-9c96-6e1a94777d3a",
"aws_cognito_region": "eu-west-1",
"aws_user_pools_id": "eu-west-1_TE5NS3Bn1",
"aws_user_pools_web_client_id": "4nr073c46hm9jcdcpnv6a9uhqm",
"oauth": {},
"aws_cognito_username_attributes": [],
"aws_cognito_social_providers": [],
"aws_cognito_signup_attributes": [
"EMAIL"
],
"aws_cognito_mfa_configuration": "OFF",
"aws_cognito_mfa_types": [
"SMS"
],
"aws_cognito_password_protection_settings": {
"passwordPolicyMinLength": 8,
"passwordPolicyCharacters": []
},
"aws_cognito_verification_mechanisms": [
"EMAIL"
],
"aws_user_files_s3_bucket": "platelet26fb7449fb884a3eb4c5fd7539c78dd301103-deev",
"aws_user_files_s3_bucket_region": "eu-west-1",
"geo": {
"amazon_location_service": {
"region": "eu-west-1",
"search_indices": {
"items": [
"plateletPlace-deev"
],
"default": "plateletPlace-deev"
}
}
}
};
export default awsmobile;
Manual configuration
No response
Additional configuration
No response
Mobile Device
Android Emulator: Samsung_Galaxy_S8_API_30
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
package.json
{
"name": "mobile",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest --watch --testTimeout=40000"
},
"dependencies": {
"@aws-amplify/datastore-storage-adapter": "^2.0.42",
"@aws-amplify/ui-react-native": "^1.2.20",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@expo/webpack-config": "^18.0.1",
"@react-native-async-storage/async-storage": "1.17.11",
"@react-native-community/netinfo": "9.3.7",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/material-bottom-tabs": "^6.2.16",
"@react-navigation/native-stack": "^6.9.13",
"@reduxjs/toolkit": "^1.9.5",
"amazon-cognito-identity-js": "^6.3.1",
"aws-amplify": "^5.3.6",
"core-js": "^3.31.1",
"expo": "~48.0.18",
"expo-blur": "~12.2.2",
"expo-clipboard": "~4.1.2",
"expo-file-system": "~15.2.2",
"expo-haptics": "~12.2.1",
"expo-sqlite": "~11.1.1",
"expo-status-bar": "~1.4.4",
"faker": "5.5.3",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"react": "18.2.0",
"react-content-loader": "^6.2.1",
"react-native": "0.71.8",
"react-native-gesture-handler": "~2.9.0",
"react-native-get-random-values": "~1.8.0",
"react-native-hold-menu": "^0.1.6",
"react-native-paper": "^5.9.1",
"react-native-paper-dates": "^0.18.12",
"react-native-reanimated": "~2.14.4",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-svg": "13.4.0",
"react-native-unimodules": "^0.14.10",
"react-native-url-polyfill": "^2.0.0",
"react-native-web": "~0.18.10",
"react-redux": "^8.1.1",
"redux-saga": "^1.2.3"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@testing-library/jest-native": "^5.4.2",
"@testing-library/react-native": "^12.1.3",
"@types/jest": "^29.5.3",
"@types/react": "~18.0.14",
"jest-expo": "^49.0.0",
"mock-async-storage": "^2.2.0",
"react-native-dotenv": "^3.4.9",
"typescript": "^4.9.4"
},
"private": true
}