I have recently been experimenting with React, GraphQL, Apollo, and Relay conforming queries.
Right now I have a GraphQL backend that has a simple GraphQL schema with some Connection queries for returning lists of entities that can be paginated using Relay server specifications.
On the front end, I've found myself repeating myself after making said queries with code that usually looks something like the below:
type isDefined = <T>(value: T | null | undefined): value is T => value !== null && value !== undefined;
export const AppleListComponent: React.FC = () => {
const { data } = useAppleQuery(); // FWIW, generating this with graphql-code-generator
const apples = data && data.apples;
const appleNodes = apples && apples.edges.map(apple => (
apple ? apple.node : undefined
)).filter(apple => isDefined(apple));
return (
<div>
{appleNodes && appleNodes.map(apple => (
<p key={apple.text}>{apple.text}</p>
))}
</div>
);
};
The song and dance of checking for presence are necessary due to the Relay spec mandating that edges and nodes be Nullable.
I find myself often repeating this transformation from a relay format to a simple array of nodes format. I decided to see if I could make a helper function that takes something that conforms to a connection interface and returns either undefined or a list of mapped nodes.
That attempt looked like this:
type Maybe<T> = T | null;
type isDefined = <T>(value: T | null | undefined): value is T => value !== null && value !== undefined;
type Connection<T> = Maybe<{ edges: Array<Maybe<{ node: Maybe<T> }>>}>;
export const connectionToNodes = <T>(connection: Connection<T>): Maybe<T[]> => (
connection && connection.edges.map(edge => (
edge ? edge.node : undefined
)).filter(isDefined)
);
Which allows the previous component to become
export const AppleListComponent: React.FC = () => {
const { data } = useAppleQuery();
const apples = data && data.apples;
const appleNodes = connectionToNodes(apples);
return (
<div>
{appleNodes && appleNodes.map(apple => (
<p key={apple.text}>{apple.text}</p>
))}
</div>
);
};
My problem here is that my type for Connection of course does not exactly match up with my type I'm passing in which has more properties that look like:
Maybe<(
{ __typename?: 'AppleConnection' }
& { edges: Array<Maybe<(
{ __typename?: 'AppleEdge' }
& { node: Maybe<{ __typename?: 'AppleType' }
& { __typename?: 'QuestionType' }
& Pick<QuestionType, 'text'>
> }
)>> }
)>
So I end up getting errors about the question being of any type, and errors that
what I am passing to connectionToNodes does not match the specified type.
My instinct is that I need to say my type T is a subtype of something else - of
course for application purposes I don't care whether it's an Apple or Pear
connection/edge/node, but I'd like for it to be ultimately inferred - any advice
on how to achieve this?
Try this helper to extract a node from a connection
export type ExtractNode<T extends { edges: any }> = NonNullable<NonNullable<T['edges'][number]>['node']>;
Usage
type Post = ExtractNode<FeedList_query['posts']>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With