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
datais 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?