Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[legacy-framework] Fix dehydrateState does not apply server side props #2617

Merged
merged 3 commits into from Aug 11, 2021

Conversation

clgeoio
Copy link
Collaborator

@clgeoio clgeoio commented Jul 31, 2021

Closes: blitz-js/legacy-framework#54

What are the changes and their implications?

The query that was setup in the dehydrated state did not have matching params (null vs undefined). Additionally, the SuperJSON deserialisation seemed to be causing some issues with the cached query being used on the SSR.

However, this change looks to undo: #2512

Bug Checklist

  • Integration test added (see test docs if needed)

Feature Checklist

@blitzjs-bot blitzjs-bot added this to In Progress in Dashboard Jul 31, 2021
@clgeoio clgeoio marked this pull request as ready for review July 31, 2021 12:28
@clgeoio clgeoio requested a review from flybayer as a code owner July 31, 2021 12:28
@blitzjs-bot blitzjs-bot moved this from In Progress to In Review in Dashboard Jul 31, 2021
@clgeoio clgeoio requested a review from Skn0tt August 1, 2021 23:35
@@ -76,7 +76,6 @@ describe("Queries", () => {
describe("DehydratedState", () => {
it("should work", async () => {
const browser = await webdriver(context.appPort, "/dehydrated-state")
await browser.waitForElementByCss("#content")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely see why this line is problematic for the test.

@@ -11,7 +11,7 @@ import {Suspense} from "react"

export const getServerSideProps: GetServerSideProps = async (ctx) => {
const queryClient = new QueryClient()
const queryKey = getQueryKey(getDate, null)
const queryKey = getQueryKey(getDate, undefined)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch spotting the wrong key argument! Maybe we can ship an abstraction with Blitz that makes these kinds of errors harder:

export const getServerSideProps = async ctx => {
  return {
    props: {
      ...(await prefetchedQueries([ getDate, undefined ], ...))
    }
  }
}
async function prefetchedQueries(...pairs) {
  const queryClient = new QueryClient()
  for (const [query, arg] of pairs) {
   const key = getQuerykey(query, arg)
   await queryClient.prefetchQuery(key, () => invokeWithMiddleware(query, arg, ctx))
  }
  return { dehydratedState: dehydrate(queryClient) }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting suggestion, I like it. It took me a while to figure it out and there was a small piece in the react-query docs that made me realize there was a mismatch.

Anyway we can make it easier for users to form the right code I am in support of :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I knew from the start we needed to make this better.

How about this?

import {prefetchQuery} from 'blitz'

export const getServerSideProps = async ctx => {

  await prefetchQuery(getData, undefined)

  return { props: {} }
}

And then we automatically add new QueryClient(), dehydrate etc via a babel plugin.

Copy link
Collaborator Author

@clgeoio clgeoio Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small suggestion would be to require that dehydratedState be passed into props: {} to avoid the edge case of a user declaring a prop dehydratedState and having overridden without them knowing by babel.
Perhaps we could rename the prop to prefetchedQuery for clarity and then transform in blitz-root?

export const getServerSideProps = async ctx => {
  const dehydratedState = await prefetchQuery(getData, undefined)
  return { props: {
    dehydratedState
  } }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The babel plugin can throw a helpful error or something in that case, right? I think having dehydratedState exposed is exposing unnecessary implementation details.

@@ -110,7 +109,7 @@ export function withBlitzAppRoot(UserAppRoot: React.ComponentType<any>) {
}, [])

const dehydratedState = props.pageProps.dehydratedState
? SuperJSON.deserialize(props.pageProps.dehydratedState)
? props.pageProps.dehydratedState
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting to me that removing the deserialisation helps in this case, since babel-plugin-superjson-next should be serializing dehydratedState:

require('babel-plugin-superjson-next'),
🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed an update in 98bdcf8, which should fix the behaviour while retaining SuperJSON support. The problem basically was that SuperJSON.deserialize was a totally wrong call - see the 98bdcf8's commit message for more context.

Copy link
Collaborator Author

@clgeoio clgeoio Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for making that change!

I think I will add a test for some of the more complex types, like Map

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestly @Skn0tt, if I swap out getDate with getComplexTypes and return something like:

export default async function getComplexTypes() {
  return {
    date: new Date(0),
    map: new Map().set("foo", "bar"),
    obj: {
      one: 1,
      child: {
        two: 2,
      },
    },
  }
}

When map: new Map() is in the returned object, the test fails:

 NoSuchElementError: no such element: Unable to locate element: {"method":"css selector","selector":"#content"}
      (Session info: headless chrome=92.0.4515.107)

      77 |     it("should work", async () => {
      78 |       const browser = await webdriver(context.appPort, "/dehydrated-state")
    > 79 |       const content = await browser.elementByCss("#content")

But when the map key is omitted from the return object, the test passes.

Any ideas here, should this be supported?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should definitely be supported. That's some weird behaviour! Will look into it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @Skn0tt.
Let me know if I can lend a hand.
I'll park this PR until you've had a look 👍

Before, it just said `SuperJSON.deserialize(props.pageProps.dehydratedState)`. This didn't work since it doesn't account for babel-plugin-superjson-next to pick different superjson keys than SuperJSON itself :( SuperJSON expects a `json` and a `meta` key, while babel-plugin-superjson-next puts the meta right into the props and calls it `_superjson`. This commit fixes that behaviour, and makes sure that dehydratedState works both for SSR and for client-side.
@flybayer
Copy link
Member

@Skn0tt what's the status on this PR?

@Skn0tt
Copy link
Member

Skn0tt commented Aug 10, 2021

@Skn0tt what's the status on this PR?

looking at it now, let's hope I find something :)

@Skn0tt
Copy link
Member

Skn0tt commented Aug 10, 2021

@flybayer @clgeoio the failure that @clgeoio found seems unrelated to this PR. Something seems to be mutating the SuperJSON before it's inlined into the __NEXT_PROPS 🤔 I'll push a reproduction test to a separate PR. This PR should be fine, and once the tests are re-run (think they failed for network connection reasons) it looks good to me.

@flybayer flybayer changed the title Fix dehyrdrated state serialization Fix dehydrateState does not apply server side props Aug 11, 2021
@flybayer flybayer merged commit 4edc560 into canary Aug 11, 2021
@flybayer flybayer deleted the fix-dehyrdrated-state-serialization branch August 11, 2021 15:22
@blitzjs-bot blitzjs-bot moved this from In Review to Done in Dashboard Aug 11, 2021
@dillondotzip dillondotzip changed the title Fix dehydrateState does not apply server side props [legacy-framework] Fix dehydrateState does not apply server side props Jul 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

dehydrateState does not apply server side props
4 participants