remix icon indicating copy to clipboard operation
remix copied to clipboard

Single Fetch: json serialization is unstable

Open lilouartz opened this issue 1 year ago • 4 comments
trafficstars

Reproduction

https://stackblitz.com/edit/remix-run-remix-ioniz1?file=app%2Froutes%2F_index.tsx

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.18.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  npmPackages:
    @remix-run/dev: * => 2.9.2 
    @remix-run/node: * => 2.9.2 
    @remix-run/react: * => 2.9.2 
    @remix-run/serve: * => 2.9.2 
    vite: ^5.1.0 => 5.2.11

Used Package Manager

npm

Expected Behavior

No error

Actual Behavior

Error:

Warning: Prop `dangerouslySetInnerHTML` did not match. Server: "[{\"@context\":\"https://schema.org\",\"name\":\"Hello, World!\"}]" Client: "[{\"name\":\"Hello, World!\",\"@context\":\"https://schema.org\"}]"

Note that the reason I am passing meta as loader property is because it was described as solution to https://github.com/remix-run/remix/issues/9418

The issue appears because serialized JSON values have different property order.

lilouartz avatar May 11 '24 21:05 lilouartz

I can somewhat workaround this by changing my server-side object declaration to match what client renders, i.e.

{
  name: 'Hello, World!',
  '@context': 'https://schema.org',
},

But this is finicky and problematic because I have ESLint rule that auto sorts properties alphabetically.

lilouartz avatar May 11 '24 21:05 lilouartz

This looks like it's coming from internal JSON.parse behavior that must order numeric object keys:

Screenshot 2024-05-28 at 11 33 29 AM

@jacob-ebey What do you think about prefixing those with _ or something in turbo-stream to make them non-numeric?

brophdawg11 avatar May 28 '24 15:05 brophdawg11

I think in my case it is the properties that start with @.

lilouartz avatar May 28 '24 16:05 lilouartz

It's not related to the name of the key. It's due to the reuse of a name property that was previously used. turbo-stream dedupes keys and values to keep the payload minimal, and it tracks them basically with an incrementing integer. When you return something like the following, the first occurrence of name gets assigned a lower integer than @context and then the second occurrence of name re-uses the earlier/lower id.

export const loader = () => {
  return {
    meta: [
      { 
        name: 'description', 
        // ^ `name` here gets assigned a lower numeric id since it's earlier
        content: 'Welcome to Remix!',
     },
      {
        'script:ld+json': [
          {
            '@context': 'https://schema.org',
             // ^ `@context` here gets assigned a higher numeric id since it's later
             name: 'Hello, World!',
             // ^ `name` here reuses the earlier (lower) numeric id
          },
        ],
      },
    ],
  };
};

This results in the problem I described above where we stringify an object with a higher numeric key, followed by a lower numeric key - and JSON.parse re-orders them when parsing, moving name before @context

brophdawg11 avatar May 29 '24 12:05 brophdawg11

I believe I have addressed this with [email protected]. I can no longer reproduce it in the stackblitz: https://stackblitz.com/edit/remix-run-remix-eliwev?file=package.json

jacob-ebey avatar Aug 10 '24 20:08 jacob-ebey

I think the serialization is still unstable in a slightly different manner - just not surfacing this specific meta/json+ld issue. Here's a simplified repro on 2.2.3: https://stackblitz.com/edit/remix-run-remix-bbrwkg?file=app%2Froutes%2F_index.tsx

brophdawg11 avatar Aug 12 '24 13:08 brophdawg11

Added some better tests and addressed it here: https://github.com/jacob-ebey/turbo-stream/pull/45

jacob-ebey avatar Aug 12 '24 19:08 jacob-ebey

👍 Awesome - the above stackblitz is fixed with [email protected] via the solution in https://github.com/remix-run/remix/issues/9421#issuecomment-2135549260.

brophdawg11 avatar Aug 13 '24 14:08 brophdawg11

[email protected] will be the default version in Remix 2.11.2 (currently in prerelease, stable release later this week) so you won't need your own turbo-stream dependency in your package.json once upgrading 👍

brophdawg11 avatar Aug 14 '24 19:08 brophdawg11

Thank you!

lilouartz avatar Aug 17 '24 08:08 lilouartz