react-apollo-hooks
react-apollo-hooks copied to clipboard
Typings: data depending on error and loading
If there is no error
and loading
is false
, is it even possible to have undefined data
?
The example given in the Readme is not possible in typescript, because data
might be undefined in the final return statement so we can't map over data.dogs
.
It would be nice if the typings could reflect this.
const Dogs = () => {
const { data, error, loading } = useQuery(GET_DOGS);
if (loading) {
return <div>Loading...</div>;
};
if (error) {
return <div>Error! {error.message}</div>;
};
return (
<ul>
{data.dogs.map(dog => (
<li key={dog.id}>{dog.breed}</li>
))}
</ul>
);
};
Not taking partials
and errors
into account, i was thinking about something along these lines (using discriminated unions, simplified example):
interface QueryHookStateLoading {
data: undefined
loading: true
error: undefined
networkStatus: NetworkStatus | undefined
}
interface QueryHookStateFailure {
data: undefined
loading: false
error: ApolloError
networkStatus: NetworkStatus | undefined
}
interface QueryHookStateSuccess<T> {
data: T
loading: false
error: undefined
networkStatus: NetworkStatus | undefined
}
type QueryHookState<T> =
| QueryHookStateLoading
| QueryHookStateFailure
| QueryHookStateSuccess<T>
If something like this was possible, it would clearly help! 👍
I think that data
is always TData | {}
in Apollo. Would it be best to do this too here?
@tleunen there is a case when data
is undefined
- when the query is skipped (it's implemented the same way in react-apollo).
@codepunkt unfortunately it couldn't be implemented that way - there is a case when loading
is true
and data is T
- when fetchMore
was invoked and hadn't finished loading additional data. Long term (when Suspense for data fetching will be ready) I'd change the API as described in https://github.com/trojanowski/react-apollo-hooks/issues/28#issuecomment-467191957 - it would solve the problem.
But only when skip
is actually provided, isn't it?
I think that
data
is alwaysTData | {}
in Apollo. Would it be best to do this too here?
I'm using typescript but my IDE hinted me that the data type is TData | undefined
. But in reality its TData | {}
. Why useQuery
gives me TData | undefined
instead of TData | {}
?
Because if the type would be TData | {}
, you would have to declare a custom type guard for the type {} instead of just writing if(!data)
.
But isn't an empty object truthy
not falsy
so you can't really just write if (!data) ...
?
const data = {};
if (!data) {
console.log("no data");
} else {
console.log("data exists"); // always the case
}
So code like this makes TypeScript happy but you actually get a runtime error, because initially data is {}
and thus accessing name
from undefined data.user
gives error.
export const App: React.FC = () => {
const { data } = useQuery<User>(GET_USER);
if (!data) {
return <LoadingView />;
}
return <div>Name: {data.user.name}</div>;
};
This would work if the data was actually undefined not an empty object (as undefined
is falsy
).
export const App: React.FC = () => {
const { data } = useQuery<User>(GET_USER);
if (!data) {
return <LoadingView />;
}
return <div>Name: {data.user.name}</div>;
};
This is the perfect example. It won't work like this.
return <div>Name: {data.user.name}</div>;
This line is the problem, as the type for data is either {} or TData (so you define a union). Typescript will complain at compile time about the access to data.user, because data can be of type {} or TData. Thus you need typeguards in your code. https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types Please don't mix {} used as a value and {} used as a type. I think that is what you are doing here.
Not sure that I follow what is the correct way to handle this then?
The problem is that the typings and actual data are different right.
When loading, the actual data is an empty object {}
but the typings say that it's either TData | undefined
. So when I know check for undefined
(or falsyness as shown in the example), TypeScript thinks it's safe to use properties from this object when in reality one would get a runtime error.
I understand that if the typings were fixed to match (TData | {}
), one would need a cumbersome type guard.
Better fix would be to make the hook return undefined
instead of an empty object so the typings would match reality and the falsyness check would work as expected.
Having trouble coming up with a suitable type guard for this?