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

RFC: Dropping data skeletons in React #1292

Open
vicary opened this issue Dec 4, 2022 · 14 comments
Open

RFC: Dropping data skeletons in React #1292

vicary opened this issue Dec 4, 2022 · 14 comments
Milestone

Comments

@vicary
Copy link
Member

vicary commented Dec 4, 2022

There have been discussions on the necessity of adding MakeNullable<T> in codegen.

It was deemed necessary back then because the original design was to always return undefined placeholder data to favor skeleton/glimmar loaders.

Since React 18 is officially out, we can count on suspense mode now. I think it is time to ask this question again.

If this is a favorable change, suspense mode will be the ONLY mode, hence dropping undefined as data skeleton altogether.

This would be a breaking change, I would like to know the impact to existing users.

Do you think it is a good idea?

Examples

Given the following GraphQL schema

type Query {
  foo: String!
  bar: String
}

It will produce the following types:

const query = useQuery();

query.foo; // string
query.bar; // string | null
@vicary vicary added this to the next milestone Dec 4, 2022
@vicary vicary pinned this issue Dec 4, 2022
@noverby
Copy link
Contributor

noverby commented Dec 4, 2022

Yes please! Would greatly improve the type safety of the library.

@natew
Copy link
Contributor

natew commented Dec 4, 2022

Wouldn’t this mean no more optimistic UI? In that before we could animate in a next page and show skeletons, whereas with this change you’d have a click… then wait… then show the next page?

Could we do just two different hooks for this? useQuerySuspense or something?

@vicary
Copy link
Member Author

vicary commented Dec 4, 2022

@natew The idea is that <Suspense> has a fallback prop for skeletons. But it does take efforts to migrate if you have a top-level skeleton component relying on the data placeholders.

I think I can retain both with some generic type magics, learning from tRPC <10 we would want to reduce recursive type inferences for IDE perf.

@natew
Copy link
Contributor

natew commented Dec 5, 2022

I get that but those fallbacks are very ad hoc, you have to basically render a totally fake page then rather than having your components automatically render a skeleton when the data is undefined. It’s a pretty neat feature of gqty imo that you get skeleton pages that 100% match your final content for free. But maybe I’m missing something.

@vicary
Copy link
Member Author

vicary commented Dec 5, 2022

@natew I would want upcoming changes to go towards a more modular design, having the core stays slim. Unfortunately the new React.use() API only works with suspense, so suspense has to be a first-class citizen.

Optimistic UI is still possible in an opt-in manner, where it becomes a wrapper over the suspense API and returns a data placeholder from another internal helper. Such that migration effort should be minimal and progressive.

What do you think?

@vasyas
Copy link

vasyas commented Dec 21, 2022

+1 for natew.
If I did get it right, it'd be an extra effort to write a fallback that will access all the fields that the main subtree is accessing. It is also a potential source of issues when those will be out-of-sync.

@vicary
Copy link
Member Author

vicary commented Dec 28, 2022

@vasyas The selection tree will be tapped directly from the observers, this is the same hooks to build our queries when we fetch. I aim to reduce friction as much as possible while moving forward, so you may very well imagine a new option in your gqty.config.js which may or may not be called legacy: true which triggers a slightly different generated client.

Other then a new option which makes the data skeleton opt-in, nothing else will be changed.

EDIT: In fact the suspense: true option may already covers and has implied what we need here.

@noverby
Copy link
Contributor

noverby commented Feb 7, 2023

@vicary is it possible to remove undefined globally with the current client?

@vicary
Copy link
Member Author

vicary commented Feb 7, 2023

@noverby There is castNotSkeleton and castNotSkeletonDeep.

But use with care, only when you are using both prepare and suspense in useQuery() will it actually skip the first skeleton render in React.

@Negan1911
Copy link

@vicary Let me know if I'm understanding wrong but castNotSkeleton and castNotSkeletonDeep will basically just remove all undefined, not really checking if the field is nullable or non-nullable right? That's why you have to be careful with it.

Isn't there a way to only let MakeNullable only apply when the field is nullable (i.e. doesn't have the ! type), if you use suspense it shouldn't be dangerous right?

@vicary
Copy link
Member Author

vicary commented Mar 16, 2023

@Negan1911 There are only specific combinations of options that we can safely drop the data skeleton, i.e.

  1. useQuery() with prepare and suspense enabled
  2. useTransactionQuery() with suspense enabled
  3. useLazyQuery() and useMutation() after invoked

This RFC tries to enforce such a usage when you enable suspense globally. It also act as a step to embrace the upcoming React Server Components, where skeletons most likely lives inside <Suspense /> blocks, so you would want a way to always render skeleton data instead of a proxy.

For existing users without suspense, I am planning to keep the behaviour unchanged.

@Negan1911
Copy link

Negan1911 commented Mar 16, 2023

@vicary Let me know if/how I can help you get this RFC started, I just came from other clients that were a pain (not only querying, but dealing with cache) and GQTY looks really promising 🙌🏻

@vicary
Copy link
Member Author

vicary commented Mar 16, 2023

@Negan1911 Really appreciate it. Be prepared because in GQty we are dealing with a lot of meta-programming, proxies, recursions and cache normalization.

DM me in Discord if you are that committed to start digging the rabbit hole, I'll answer your questions along the way.

To implement this RFC, we'll need to tweak the CLI codegen, add a new helper in core, and throw in React when appropriate.

codegen

Add MakeNullable only when suspense is disabled.

The helper function

You'll need to understand the concept of the new core to know what this sentence means --- "A Skeleton helper is only an accessor with an empty cache as it's scoped context."

The new core

I just went through all of these and trimmed down and much as possible in #1544, hopefully it'll allow more good-first issues in the future. It is still pre-alpha with a lot of testing to be done, but the foundation is there.

@vicary
Copy link
Member Author

vicary commented May 4, 2024

What now?

The current plan is to export two schema types,

  1. One with optional scalars to reflect the state before fetch, and
  2. Another schema without optional to reflect the results.

Where?

A new non-nullable schema

This can only be applied to those places we are confident that data exists, such as

  1. The return type of resolve
  2. The yield type of subscribe
  3. The return type of useQuery ONLY when prepare and suspense are specified.

The current schema

This is the current schema and it will stay unchanged for the rest of the API.

@vicary vicary unpinned this issue May 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants