Closed nemonemi closed 3 years ago
Maybe because GraphQL allows you to NOT select any fields, and maybe gqless by design only selects the properties on-the-fly when you use it?
@vicary, it is not clear to me if this was a response or an addition to my question. Do you actually know as to why things are as I've described them?
I don't speak for the original design, but the undefined
part makes sense to me as a user.
Having a type with optional fields doesn't give any more freedom than a type with non-optional fields when querying.
This is a type definition for which there will always be data, and if the property is undefined
, that data cannot be accessed directly, but through a guard.
That part doesn't make sense to me.
Maybe when you are using something like suspense on React, it does not make sense to have undefined
. But for other cases, it does.
On a non-suspense case when there is a component that is recently fetching query.user.id
and you try to access this, it will be undefined
at first render.
A nullable field get's null
from api, never undefined
. So we can distinguish them.
I also don't speak speak for the design.
But I guess even on suspense cases this does make sense:
It first renders and intercepts when you try to access a query property. It immediately suspends so you will never see the undefined
's.
But they are there, and if you use this undefined
where it shouldn't, even with suspense it will produce errors. I guess.
Yes, I understand that this is how the library operates, however, this makes re-using the generated types difficult.
Instead of just re-using the generated types (which is one of the strengths of this library), one would have to transform the types manually to make them behave as the application needs.
For example, a component is loaded and the entity that it is loaded with contains the ID property. If it didn't contain it, then it wouldn't have loaded the screen with its data.
Now, I'm performing a mutation, and I'm passing that ID property, but it complains that it cannot accept string | undefined
. At that point, the ID property logically cannot happen to be string | undefined
, yet the type states that it is.
This then calls for casting the type, which breaks the confidence of the application. It breaks the data-type flow, which is bad!
Is there already a pattern or a recipe to handle this type of behavior in the application and increase confidence?
The whole point of generating types from the schema is to create a contract that reflects the existence of data, and here the library breaks that contract and introduces complexity.
This is probably the only thing preventing me from using this over something like Apollo Client. The codegen performed by the apollo tooling and graphql-codegen doesn't have this issue. However for things like Apollo Client you are querying an object and doing checks like if (!data) return <Text>Loading...</Text>
but Suspense solves that so I don't see why this can't be changed.
I don't think suspense solves that... Does it?
With suspense you just never see it undefined, but at first access before throwing promises to build the schema you are trying to fetch, it first is undefined.
If you try to make something that requires it not to be undefined on the same render, I think it will error. Correct me if I'm mistaken, I'm not using it on React, but maybe you can console.log
values to get the real behaviour
@outerlook I suppose you're right in that on the first render pass its undefined but from a practical standpoint its a pretty big drawback that all fields are made optional. As a frontend developer its no longer clear which fields actually need fallbacks vs which will always be available.
When looking at things like the official react docs they aren't doing things like user?.name
or if (user)
because Suspense "suspends" rendering until the data is available. If you console.log
it though you'll probably see undefined initially so I don't know if there is a way to address that from a technical purist standpoint.
Is there an actual practical purpose for those values being undefined though? If I'm doing an API call the oldschool way with manual typescript types I'm not making all my fields optional because the suspended component won't actually render without them.
I'm curious to see if Relay does anything to alleviate that or if its the same - will try to take a look when I have time.
I know there is a difference that may be related to that between gqless and the other graphql clients.
Other clients usually you first ask all the data you want. This data becomes a full promise that can be thrown and then waited for before rendering again.
think data:
{
user: {
name
age
}
}
if I try to access user.name it sure will be a value, no need for checking optional, in traditional clients you already asked for that shape of data at the start.
But for gqless things are detected and then fetched if you try to access them.
then if you try to use like that:
const userNamesUppercased = user.name?.uppercase()
you should get an error if you did not put that ?
right there. Because it would try to call uppercase()
on undefined
But there are somethings you should notice:
1 - only scalars become Maybe<VALUE>
, objects don't (only if it was already optional).
So user won't be cast to Maybe<User>
, only name and age.
2 - At least at my projects, graphql requests can't be answered with undefined
. When there's no data, the data is null
. So you can check if some data is really not there by calling user?.name === null
.
This makes gqless not so immediate plug'n'play substitute to other clients. But in my experience, the decision of pulling the data you need before using, in contrast to pushing as the other libraries pays this difference off...
@samdenty, @PabloSzx, Could we get an official response to this problem, please?
@outerlook I think I was confused about how well gqless is able to detect usage and expected that a promise would be thrown as soon as a property is accessed but that makes sense - thanks!
@samdenty, @PabloSzx, Could we get an official response to this problem, please?
the official response is what outerlook mentioned, all the scalars being undefined
is for the types to be really the same as in actual runtime. The example:
user.name?.toUpperCase()
is very clear, if the name is not nullable (it cannot be NULL), it still won't be there when you first try to access it, even in the React Suspense world.
If you can be completely sure that when you are going to actually manipulate the scalars the types are only making noise, you can always use the type casters https://gqty.dev/docs/client/helper-functions#type-casters, in the type-only world you can use what those casters use NotSkeletonDeep<T>
and NotSkeleton<T>
.
As the previous link also mentioned, further development is going to be made in the new home, gqty https://github.com/gqty-dev/gqty
I have a schema that defines property as non-nullable, so why is the type generated saying
string | undefined
?The whole object could be CustomType | undefined because it might not be available during loading, but not its properties.
/schema.graphql
/schema.generated.ts