flightcontrolhq / superjson

Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.
https://www.flightcontrol.dev?ref=superjson
MIT License
4.02k stars 87 forks source link

Getting "TypeError: Cannot read property 'queries' of undefined" #196

Closed dgrcode closed 1 year ago

dgrcode commented 2 years ago

I've been trying to debug this, but after some time spent looking at breakpoints I can't manage to understand why the object to be serialised is coming as undefined.

The complete stack trace:

error - TypeError: Cannot read property 'queries' of undefined
    at /Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/accessDeep.js:28:24
    at Array.forEach (<anonymous>)
    at Object.getDeep (/Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/accessDeep.js:27:10)
    at apply (/Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/plainer.js:59:35)
    at /Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/util.js:53:16
    at Array.forEach (<anonymous>)
    at Object.forEach (/Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/util.js:51:28)
    at Object.applyReferentialEqualityAnnotations (/Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/plainer.js:74:16)
    at Object.deserialize (/Users/daniel/projects/bootstrapped/visualbolt/node_modules/superjson/dist/index.js:44:28)
    at deserializeProps (/Users/daniel/projects/bootstrapped/visualbolt/node_modules/babel-plugin-superjson-next/dist/tools.js:110:32) {
  page: '/view/new'
}

I'm using NextJS and react query. The problem seems to come when superjson tries to serialize react query's dehydrated state.

Debugging this issue I've found what object is exactly causing the problem, but I can't manage to find a solution debugging the dist files. I'll show what works, and what breaks.

Working code

const queryClient = new QueryClient()
const templates = await prisma.template.findMany({
  where: { ownerId: userId },
  include: { fields: true },
})
queryClient.setQueryData(['templates'], templates)

return {
  props: { dehydratedState: dehydrate(queryClient) },
}

These are the JSON representation of relevant objects there:

Breaking code

const queryClient = new QueryClient()
const templates = await prisma.template.findMany({
  where: { ownerId: userId },
  include: { fields: true },
})
queryClient.setQueryData(['templates'], templates)

+ const t = templates[0]
+ queryClient.setQueryData(['templates', t.id], t)

return {
  props: { dehydratedState: dehydrate(queryClient) },
}

These are the JSON representation of the relevant objects here:

Differences

To simplify finding out what's going on, this is the diff between the two dehydratedStates

@@ -23,7 +23,7 @@
           }
         ],
         "dataUpdateCount": 1,
-        "dataUpdatedAt": 1660642973913,
+        "dataUpdatedAt": 1660643358366,
         "error": null,
         "errorUpdateCount": 0,
         "errorUpdatedAt": 0,
@@ -37,6 +37,42 @@
         "templates"
       ],
       "queryHash": "[\"templates\"]"
+    },
+    {
+      "state": {
+        "data": {
+          "id": "cl6mlkijv00000glajl1xljzl",
+          "label": "foo",
+          "ownerId": "cl6cdyozp0007jbul3o4oqoox",
+          "createdAt": "2022-08-09T19:50:25.003Z",
+          "updatedAt": "2022-08-09T19:50:25.019Z",
+          "fields": [
+            {
+              "id": "cl6mlkik800010glagppvr24i",
+              "label": "Property",
+              "type": "NUMBER",
+              "templateId": "cl6mlkijv00000glajl1xljzl",
+              "createdAt": "2022-08-09T19:50:25.003Z",
+              "updatedAt": "2022-08-09T19:50:25.019Z"
+            }
+          ]
+        },
+        "dataUpdateCount": 1,
+        "dataUpdatedAt": 1660643358372,
+        "error": null,
+        "errorUpdateCount": 0,
+        "errorUpdatedAt": 0,
+        "fetchFailureCount": 0,
+        "fetchMeta": null,
+        "isInvalidated": false,
+        "status": "success",
+        "fetchStatus": "idle"
+      },
+      "queryKey": [
+        "templates",
+        "cl6mlkijv00000glajl1xljzl"
+      ],
+      "queryHash": "[\"templates\",\"cl6mlkijv00000glajl1xljzl\"]"
     }
   ]
 }

The timestamp difference of dataUpdatedAt is just the cache "updated at" timestamp, not the actual "updated at" from the database object.

Skn0tt commented 2 years ago

Hey @dgrcode, thanks for reporting this! Seems like something we should look into. Reading through the lengths of this, I think it'd be very helpful to have a failing reproduction test for this. Could you maybe reproduce this as a failing test in https://github.com/blitz-js/superjson/blob/main/src/index.test.ts, and open a PR with it? That'd allow for easier fixing, I think.

dgrcode commented 2 years ago

Sure! I'll get to it as soon as possible. That's hopefully later today or tomorrow 🤞

Thanks for replying so fast :)