I am using graphql codegen together with urql to ensure back-to-back ts safety. But, after generating types and queries, I am having an issue typing the component props when passing data down to the component. Here is an example:
This is the query type generated:
export type AnnotationsQuery = { __typename?: 'Query', threads?: Array<{ __typename?: 'Thread', annotation: { __typename?: 'Annotation', id: string, x: number, y: number, width: number, height: number, key: number, color: string, stroke: number, style: string } }> | null };
Normally, when mapping over threads
and passing thread
as a prop to the component it could be typed as AnnotationsQuery["threads"][number]
but in this case this will not work as the threads?: Array<>
is optional, making AnnotationsQuery["threads"][number]
not a proper type or any
.
Is there a better/correct way to type a single thread
even if the threads
is optional?
You can use NonNullable
type ThreadOptional = NonNullable<AnnotationsQuery["threads"]>[number] | undefined;
interface Props {
thread: ThreadOptional;
}
const ExampleComponent = ({ thread }: Props) => {
// your component logic here
};
check it against simple unit test:
describe("ThreadOptional", () => {
it("should correctly type a valid thread", () => {
const thread: ThreadOptional = {
__typename: "Thread",
annotation: {
__typename: "Annotation",
id: "123",
x: 0,
y: 0,
width: 10,
height: 10,
key: 1,
color: "#000",
stroke: 1,
style: "solid"
}
};
expect(thread).toBeDefined();
expect(thread?.annotation.id).toBe("123");
});
it("should correctly type an undefined thread", () => {
const thread: ThreadOptional = undefined;
expect(thread).toBeUndefined();
});
it("should handle null or undefined AnnotationsQuery['threads']", () => {
const query: AnnotationsQuery = { __typename: "Query", threads: null };
const thread1: ThreadOptional = query.threads?.[0];
expect(thread1).toBeUndefined();
const thread2: ThreadOptional = undefined;
expect(thread2).toBeUndefined();
});
});