blitz-js / legacy-framework

MIT License
3 stars 2 forks source link

I would like an alternative for getServerSideProps and getStaticProps #132

Closed nitedani closed 2 years ago

nitedani commented 2 years ago

What do you want and why?

I would like an alternative for getServerSideProps and getStaticProps. They are too much boilerplate. The dehydration/hydration should be centralized.

Right now it looks like this:

export const getServerSideProps = async (context) => {
  const queryClient = new QueryClient()
  // IMPORTANT: the second argument to getQueryKey must exactly match the second argument to useQuery down below
  const queryKey = getQueryKey(getProject, {
    where: { id: context.params?.projectId },
  })
  await queryClient.prefetchQuery(queryKey, () =>
    invokeWithMiddleware(
      getProject,
      { where: { id: context.params?.projectId } },
      context
    )
  )

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

function ProjectPage(props) {
  const [project] = useQuery(getProject, { where: { id: props.id } })
  return <div>{project.name}</div>
}

export default ProjectPage

It would become this:


function ProjectPage(props) {
  const [project] = useQuery(getProject, { where: { id: props.id } })
  return <div>{project.name}</div>
}

export default ProjectPage

Just for comparsion, in nuxt3 it looks like this:

<script setup>
const { data } = await useAsyncData('project', () => $fetch('/api/project'))
</script>

<template>
    <div>{data.name}</div>
</template>

I would like blitz to go in the same direction and create a function something like:

useAsyncData<T>(key:string, cb: () => T | Promise<T>): { data, loading, error, refetch, ... }

On the server side, the response to the browser would be sent when all of the useAsyncData callbacks resolved. The transfer of state from server to browser would be handled by the framework.

beerose commented 2 years ago

Hey @nitedani, this is the new setup for hydration with the Blitz Toolkit: https://canary.blitzjs.com/docs/query-usage#prefetching (we removed the boilerplate and you don't have to pass dehydratedState explicitly). How does this look to you?

nitedani commented 2 years ago

If this is what I think it is, it's good. How would I use this?

import { invoke } from "@blitzjs/rpc"
function ProjectPage(props) {
  const project = await invoke(getProject, { where: { id: props.id } })
  return <div>{project.name}</div>
}

Is it simple as that?

beerose commented 2 years ago

No, you'd still need getServerSideProps/getStaticProps, but we removed the prefetching boilerplate required:

import { gSSP } from "app/blitz-server"
import getProject from "app/projects/queries/getProject"

export const getServerSideProps = gSSP(async ({ ctx }) => {
  await ctx.prefetchBlitzQuery(getProject, {
    where: { id: context.params?.projectId },
  })

  return { props: {} }
})

function ProjectPage(props) {
  const [project] = useQuery(getProject, { where: { id: props.id } })
  return <div>{project.name}</div>
}

export default ProjectPage

It's not exactly what you mentioned in your issue, but it's much simpler than it used to be.

nitedani commented 2 years ago

It is good that you removed some boilerplate. I don't want to sound peasant, but in my opinion this is still too repetitive code for the developers. I would need to jump in the code back and forth the prefetchBlitzQuery and useQuery. Today I read about react 18 Suspense here: https://github.com/reactwg/react-18/discussions/37. I like where they are going with that. Maybe it will make possible what now seems impossible 😄 I'll close this issue and experiment what's possible with suspense.

I found the way to do it how I like it 😄 This is using vite-plugin-ssr. I just had to enable suspense for react-query. The ctx.reactQueryState is transferred to the browser in the html, then loaded into react-query cache. Its just a proof of concept, but the future looks promising to me.

import { Suspense } from "react";
import { useQuery as _useQuery, useQueryClient, dehydrate } from "react-query";
import { usePageContext } from "../../renderer/usePageContext";

function useQuery<T>(
  key: string,
  queryFn: () => Promise<T>
): { data: T | undefined } {
  const { data } = _useQuery(key, queryFn, { staleTime: 99999 });
  if (import.meta.env.SSR) {
    const queryClient = useQueryClient();
    const ctx = usePageContext();
    const dehydratedState = dehydrate(queryClient);
    Object.assign(ctx.reactQueryState, dehydratedState);
  }
  return { data };
}

function Test() {
  const { data } = useQuery("test", () =>
    fetch("https://httpbin.org/get").then((res) => res.json())
  );

  return <div>{JSON.stringify(data)}</div>;
}

function App() {
  return (
    <div>
      <Suspense fallback={<h1>Loading...</h1>}>
        <Test />
      </Suspense>
    </div>
  );
}
export { App };