-
Notifications
You must be signed in to change notification settings - Fork 786
[Question] How to extend Query
component
#1980
Comments
You will still need to pass the query prop into the component. When you extend the Query class with the Data and Variables types, it make it so the data and variables are typed. You get errors if you try to pass in variables that don't match the provided type or try to access fields on data in an unsafe way. A lot of the other values passed to the children render prop also benefit from the type info (refetch, updateQuery, etc). Type information isn't going to be able to replace the passing of a prop though. |
Thanks @TLadd. I thought there might be something I could do in the body of the extended class to set the query. class UserQuery extends Query<
User.Query,
User.Variables
> {
... predefine what query to run here
} |
I've been using query components which wraps the Apollo Query component and query description: import React, { Component, ReactNode } from "react"
import { Query as ApolloQuery } from "react-apollo"
import gql from "graphql-tag"
interface Props {
id: string
children: (data?: Data) => ReactNode
}
interface Data {
user: {
id: string
name: string
}
}
interface Variables {
id: string
}
export class UserQuery extends Component<Props> {
render() {
const { id, children } = this.props
return (
<Query query={query} variables={{ id }}>
{result => children(result.data)}
</Query>
)
}
}
class Query extends ApolloQuery<Data, Variables> {}
const query = gql`
query User($id: String!) {
user(id: $id) {
id
name
}
}
` The you can use the query this way: <UserQuery id={"0"}>
{data => ... }
</UserQuery> |
When the next version of Typescript comes out that includes this patch: microsoft/TypeScript#22415, it should hopefully be even easier: render() {
return (
<Query<positionPageQuery, positionPageQueryVariables> query={POSITION_PAGE} variables={{ slug }}>
{({ loading, error, data }) => {
// data is strongly typed.
})
</Query>
)
} |
@majelbstoat Thats great this seems like it will solve my problem |
Hey, maybe this will be hepful to someone, but if you want to create your own Query component to handle queries in a consistant way across your app, I've done this with typescript. The idea is that by default you render the spinner/error/empty state in the same way everywhere by default, so this reduces you from writing boilerplate that is similar across app screens (I use this on a RN app), and you only need to render the happy path. type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type OmitChildren<T> = Omit<T,"children">
interface AppQueryProps<Data,Variables> {
children: (data: Data,result: QueryResult<Data,Variables>) => ReactNode
renderNetworkStatus?: (networkStatus: NetworkStatus, result: QueryResult<Data,Variables>) => ReactNode
renderError?: (error: Error, result: QueryResult<Data,Variables>) => ReactNode
renderNoData?: (result: QueryResult<Data,Variables>) => ReactNode
}
export class AppQuery<Data, Variables> extends React.Component<OmitChildren<QueryProps<Data, Variables>> & AppQueryProps<Data,Variables>> {
defaultRenderNetworkStatus = (networkStatus: NetworkStatus, _result: QueryResult<Data, Variables>) => {
if (networkStatus === NetworkStatus.loading) {
return (
<Centered style={{ flex: 1 }}>
<AppSpinner/>
</Centered>
);
}
return null;
};
defaultRenderError = (_error: Error, result: QueryResult<Data, Variables>) => {
if (!result.data) {
return (
<Centered style={{ flex: 1 }}>
<AppText>Error</AppText>
</Centered>
);
}
return null;
};
defaultRenderNoData = (_result: QueryResult<Data, Variables>) => {
return (
<Centered style={{ flex: 1 }}>
<AppText>No data</AppText>
</Centered>
);
};
render() {
const {children, renderNetworkStatus, renderError, renderNoData,...queryProps} = this.props;
return (
<Query<Data,Variables>
{...queryProps}
>
{(result: QueryResult<Data,Variables>) => {
const networkStatusNode = (renderNetworkStatus || this.defaultRenderNetworkStatus)(result!.networkStatus,result);
if ( networkStatusNode ) {
return networkStatusNode;
}
const errorNode = result.error ? (renderError || this.defaultRenderError)(result!.error!,result) : undefined;
if ( errorNode ) {
return errorNode;
}
const noDataNode = !result.data ? (renderNoData || this.defaultRenderNoData)(result) : undefined;
if ( noDataNode ) {
return noDataNode;
}
return children(result.data!,result);
}}
</Query>
);
}
} |
@slorber that looks like what i was searching for though i get the following error with the current typescript version and your example. any idea?
|
I am wrapping my query components the following way: type QueryType = Omit<QueryProps<getSpacesByUserId, getSpacesByUserIdVariables>, 'query'>
export const GetSpacesByUserId: React.SFC<QueryType> = props => (
<Query<getSpacesByUserId, getSpacesByUserIdVariables> {...props} query={query} />
) And the Omit type i have declared in a types.d.ts file and it looks like this: type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> The QueryType just takes the props from the Query Component and removes the query prop. So now it can be used like this: <GetSpacesByUserId variables={{userId: this.props.authUserId}}>{getSpacesQuery => {
// ...
}}<GetSpacesByUserId/> |
@OneCyrus The error you are seeing seems to be due to a change in @Herlevsen does that actually work with latest function test<getSpacesByUserId, getSpacesByUserIdVariables>(
query: DocumentNode,
) {
type QueryType = Omit<
QueryProps<getSpacesByUserId, getSpacesByUserIdVariables>,
'query'
>;
// Error
const GetSpacesByUserId: React.SFC<QueryType> = (props) => (
<Query<getSpacesByUserId, getSpacesByUserIdVariables>
{...props}
query={query}
/>
);
} |
Within a typescript project I am looking to achieve something like this
Following on from the docs I am trying to get
UserQuery
to extend theQuery
component in a way that means I do not have to pass thequery
prop in manually.UserQuery
should already know what query to run.The text was updated successfully, but these errors were encountered: