Datastore Obj.copyOf function doesn't accept spread operators, but returns no errors and fails to apply changes to copied object
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
- [X] I have read the guide for submitting bug reports.
- [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
React, Next.js
Amplify APIs
DataStore
Amplify Categories
api
Environment information
# Put output below this line
System:
OS: macOS 13.5.1
CPU: (12) arm64 Apple M2 Pro
Memory: 45.55 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.17.1 - ~/.local/share/rtx/installs/node/18.17.1/bin/node
npm: 9.8.1 - ~/.local/share/rtx/installs/node/18.17.1/bin/npm
Browsers:
Chrome: 116.0.5845.179
Safari: 16.6
npmPackages:
@ampproject/toolbox-optimizer: undefined ()
@auth0/auth0-spa-js: 2.0.8 => 2.0.8
@aws-amplify/ui-react: ^5.3.0 => 5.3.0
@aws-amplify/ui-react-internal: undefined ()
@aws-amplify/ui-react-storage: ^2.3.0 => 2.3.0
@babel/core: undefined ()
@babel/runtime: 7.15.4
@edge-runtime/cookies: 3.2.1
@edge-runtime/ponyfill: 2.3.0
@edge-runtime/primitives: 3.0.1
@emotion/cache: 11.11.0 => 11.11.0
@emotion/react: 11.11.1 => 11.11.1
@emotion/server: 11.11.0 => 11.11.0
@emotion/styled: 11.11.0 => 11.11.0
@fontsource/inter: 5.0.4 => 5.0.4
@fontsource/plus-jakarta-sans: 5.0.4 => 5.0.4
@fontsource/roboto-mono: 5.0.4 => 5.0.4
@fullcalendar/core: 6.1.8 => 6.1.8
@fullcalendar/daygrid: 6.1.8 => 6.1.8
@fullcalendar/interaction: 6.1.8 => 6.1.8
@fullcalendar/list: 6.1.8 => 6.1.8
@fullcalendar/react: 6.1.8 => 6.1.8
@fullcalendar/timegrid: 6.1.8 => 6.1.8
@fullcalendar/timeline: 6.1.8 => 6.1.8
@hapi/accept: undefined ()
@mapbox/mapbox-gl-style-spec: 13.29.0-dev
@mui/icons-material: 5.13.7 => 5.13.7
@mui/lab: 5.0.0-alpha.135 => 5.0.0-alpha.135
@mui/material: 5.13.7 => 5.13.7
@mui/system: 5.13.7 => 5.13.7
@mui/x-date-pickers: 6.9.1 => 6.9.1
@napi-rs/triples: undefined ()
@next/font: undefined ()
@next/react-dev-overlay: undefined ()
@opentelemetry/api: undefined ()
@react-pdf/renderer: 3.1.12 => 3.1.12
@reduxjs/toolkit: 1.9.5 => 1.9.5
@reduxjs/toolkit-query: 1.0.0
@reduxjs/toolkit-query-react: 1.0.0
@segment/ajv-human-errors: undefined ()
@svgr/webpack: 8.0.1 => 8.0.1
@types/lodash.debounce: 4.0.7 => 4.0.7
@types/lodash.isequal: 4.5.6 => 4.5.6
@types/lodash.throttle: 4.1.7 => 4.1.7
@types/node: 20.4.0 => 20.4.0
@types/nprogress: 0.2.0 => 0.2.0
@types/numeral: 2.0.2 => 2.0.2
@types/react: 18.2.14 => 18.2.14
@types/react-beautiful-dnd: 13.1.4 => 13.1.4
@types/react-dom: 18.2.6 => 18.2.6
@types/react-draft-wysiwyg: 1.13.4 => 1.13.4
@types/react-redux: 7.1.25 => 7.1.25
@types/react-slick: 0.23.10 => 0.23.10
@types/react-syntax-highlighter: 15.5.7 => 15.5.7
@untitled-ui/icons-react: 0.1.1 => 0.1.1
@vercel/nft: undefined ()
@vercel/og: undefined ()
acorn: undefined ()
amphtml-validator: undefined ()
anser: undefined ()
apexcharts: 3.41.0 => 3.41.0
arg: undefined ()
assert: undefined ()
async-retry: undefined ()
async-sema: undefined ()
aws-amplify: ^5.3.10 => 5.3.10
babel-packages: undefined ()
browserify-zlib: undefined ()
browserslist: undefined ()
buffer: undefined ()
bytes: undefined ()
chalk: undefined ()
ci-info: undefined ()
cli-select: undefined ()
client-only: 0.0.1
comment-json: undefined ()
compression: undefined ()
conf: undefined ()
constants-browserify: undefined ()
content-disposition: undefined ()
content-type: undefined ()
cookie: undefined ()
cross-spawn: undefined ()
crypto-browserify: undefined ()
css.escape: undefined ()
data-uri-to-buffer: undefined ()
date-fns: 2.30.0 => 2.30.0
debug: undefined ()
devalue: undefined ()
domain-browser: undefined ()
draft-js: 0.11.7 => 0.11.7
edge-runtime: undefined ()
eslint: 8.44.0 => 8.44.0
eslint-config-next: 13.4.8 => 13.4.8
eslint-config-prettier: 8.8.0 => 8.8.0
events: undefined ()
find-cache-dir: undefined ()
find-up: undefined ()
firebase: 9.23.0 => 9.23.0
firebase/analytics: undefined ()
firebase/app: undefined ()
firebase/app-check: undefined ()
firebase/auth: undefined ()
firebase/auth/cordova: undefined ()
firebase/auth/react-native: undefined ()
firebase/compat: undefined ()
firebase/compat/analytics: undefined ()
firebase/compat/app: undefined ()
firebase/compat/app-check: undefined ()
firebase/compat/auth: undefined ()
firebase/compat/database: undefined ()
firebase/compat/firestore: undefined ()
firebase/compat/functions: undefined ()
firebase/compat/installations: undefined ()
firebase/compat/messaging: undefined ()
firebase/compat/performance: undefined ()
firebase/compat/remote-config: undefined ()
firebase/compat/storage: undefined ()
firebase/database: undefined ()
firebase/firestore: undefined ()
firebase/firestore/lite: undefined ()
firebase/functions: undefined ()
firebase/installations: undefined ()
firebase/messaging: undefined ()
firebase/messaging/sw: undefined ()
firebase/performance: undefined ()
firebase/remote-config: undefined ()
firebase/storage: undefined ()
formik: 2.4.2 => 2.4.2
fresh: undefined ()
get-orientation: undefined ()
glob: undefined ()
gray-matter: 4.0.3 => 4.0.3
gzip-size: undefined ()
http-proxy: undefined ()
http-proxy-agent: undefined ()
https-browserify: undefined ()
https-proxy-agent: undefined ()
i18next: 23.2.7 => 23.2.7
icss-utils: undefined ()
ignore-loader: undefined ()
image-size: undefined ()
is-animated: undefined ()
is-docker: undefined ()
is-wsl: undefined ()
jest-worker: undefined ()
json5: undefined ()
jsonwebtoken: undefined ()
loader-runner: undefined ()
loader-utils: undefined ()
lodash.curry: undefined ()
lodash.debounce: 4.0.8 => 4.0.8
lodash.isequal: 4.5.0 => 4.5.0
lodash.throttle: 4.1.1 => 4.1.1
lru-cache: undefined ()
mapbox-gl: 2.15.0 => 2.15.0
micromatch: undefined ()
mini-css-extract-plugin: undefined ()
mui-one-time-password-input: 1.1.2 => 1.1.2
nanoid: undefined ()
native-url: undefined ()
neo-async: undefined ()
next: 13.4.8 => 13.4.8
node-fetch: undefined ()
node-html-parser: undefined ()
nprogress: 0.2.0 => 0.2.0
numeral: 2.0.6 => 2.0.6
ora: undefined ()
os-browserify: undefined ()
p-limit: undefined ()
path-browserify: undefined ()
platform: undefined ()
postcss-flexbugs-fixes: undefined ()
postcss-modules-extract-imports: undefined ()
postcss-modules-local-by-default: undefined ()
postcss-modules-scope: undefined ()
postcss-modules-values: undefined ()
postcss-preset-env: undefined ()
postcss-safe-parser: undefined ()
postcss-scss: undefined ()
postcss-value-parser: undefined ()
prettier: 3.0.0 => 3.0.0
process: undefined ()
prop-types: 15.8.1 => 15.8.1
punycode: undefined ()
querystring-es3: undefined ()
raw-body: undefined ()
react: 18.2.0 => 18.2.0
react-apexcharts: 1.4.0 => 1.4.0
react-beautiful-dnd: 13.1.1 => 13.1.1
react-builtin: undefined ()
react-dom: 18.2.0 => 18.2.0
react-dom-builtin: undefined ()
react-dom-experimental-builtin: undefined ()
react-draft-wysiwyg: 1.15.0 => 1.15.0
react-dropzone: 14.2.3 => 14.2.3
react-experimental-builtin: undefined ()
react-hot-toast: 2.4.1 => 2.4.1
react-i18next: 13.0.1 => 13.0.1
react-is: 18.2.0
react-map-gl: 7.1.1 => 7.1.1
react-markdown: 8.0.7 => 8.0.7
react-quill: 2.0.0 => 2.0.0
react-redux: 8.1.1 => 8.1.1 (7.2.9)
react-refresh: 0.12.0
react-server-dom-webpack-builtin: undefined ()
react-server-dom-webpack-experimental-builtin: undefined ()
react-slick: 0.29.0 => 0.29.0
react-syntax-highlighter: 15.5.0 => 15.5.0
redux: 4.2.1 => 4.2.1
redux-devtools-extension: 2.13.9 => 2.13.9
redux-thunk: 2.4.2 => 2.4.2
regenerator-runtime: 0.13.4
sass-loader: undefined ()
scheduler-builtin: undefined ()
scheduler-experimental-builtin: undefined ()
schema-utils: undefined ()
semver: undefined ()
send: undefined ()
server-only: 0.0.1
setimmediate: undefined ()
shell-quote: undefined ()
simplebar-react: 3.2.4 => 3.2.4
slick-carousel: 1.8.1 => 1.8.1
source-map: undefined ()
stacktrace-parser: undefined ()
stream-browserify: undefined ()
stream-http: undefined ()
string-hash: undefined ()
string_decoder: undefined ()
strip-ansi: undefined ()
stylis: 4.3.0 => 4.3.0 (4.2.0)
stylis-plugin-rtl: 2.1.1 => 2.1.1
tar: undefined ()
terser: undefined ()
text-table: undefined ()
timers-browserify: undefined ()
tty-browserify: undefined ()
typescript: 5.1.6 => 5.1.6
ua-parser-js: undefined ()
undici: undefined ()
unistore: undefined ()
util: undefined ()
vm-browserify: undefined ()
watchpack: undefined ()
web-vitals: undefined ()
webpack: undefined ()
webpack-sources: undefined ()
ws: undefined ()
yup: 1.2.0 => 1.2.0
zod: undefined ()
npmGlobalPackages:
@aws-amplify/cli: 12.3.0
alfred-uuid: 3.0.0
apollo: 2.34.0
corepack: 0.18.0
graphql-introspection-json-to-sdl: 1.0.3
graphql: 16.8.0
npm: 9.8.1
openapi-to-graphql-cli: 3.0.5
pm2: 5.3.0
tsup: 7.2.0
Describe the bug
When using Datastore.save of a copy of an existing object as follows it works as expected
updatedItem = {foo:'bar'}
orig = await DataStore.query(Obj, id)
await DataStore.save(Obj.copyOf(orig, item => {
Object.assign(item,updatedItem)
}
However, in a React / Next.js environment, spread operators are heavily used, and it would be expected this would be supported as well:
updatedItem = {foo:'bar'}
orig = await DataStore.query(Obj, id)
await DataStore.save(Obj.copyOf(orig, item => {
item = {...item,...updatedItem}
}
However, this does not work. No error is given by the DataStore function - an un-mutated item is instead attempted to save (with no changes or increments of the object's version). I understand that spread operators vs object assignment have different underlying mechanisms, and spread operators create a new object vs mutating the original, but it would be ideal if this were supported.
Doing assignment from the original object to the new object does not work either.
await DataStore.save(Obj.copyOf(orig, item => {
item = {...orig,...updatedItem}
}
Expected behavior
Spread operators should be supported in mutating a copy of an object via Datastore functions.
Perhaps the Datastore copyOf function should just return item in whatever form? I imagine mutation tracking is leveraged to only submit the fields via GraphQL that are individually mutated?
At a minimum, it should be documented that spread operators are not supported in this function as they're heavily used in front end UI frameworks.
In an ideal world, we'd be able to just receive the object, mutate/manipulate it outside of the Datastore functions, and then apply via a separate save function.
E.g.
updatedItem = {foo:'bar'}
orig = await DataStore.query(Obj, id)
newItem = {...orig,...updatedItem}
await DataStore.save(newItem)
DataStore.save should be able to validate object schema against the model vs relying on a copy. Given the copy can already be done separately from the save function, the working copy may be stale from local/backend datastore versions at the time of save anyways.
Reproduction steps
Use a spread operator to assign values to a copied item and attempt to save. The save will have no effect and generate no errors.
updatedItem = {foo:'bar'}
orig = await DataStore.query(Obj, id)
await DataStore.save(Obj.copyOf(orig, item => {
item = {...item,...updatedItem}
}
Code Snippet
// Put your code below this line.
Log output
// Put your logs below this line
NA
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
No response
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
No response
Hello, @geekbleek 👋. Appreciate you opening this issue and will be marking it as a feature request for now regarding the support of spread operators when mutating a copy of an object. To your point, if it's not supported, we should have this mentioned within our documentation. I'll review this along with the lack of errors being thrown when this happens with the team and report back!